diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml deleted file mode 100644 index 6504ce060..000000000 --- a/.buildkite/pipeline.yml +++ /dev/null @@ -1,7 +0,0 @@ -steps: - - - label: "Dummy workflow" - commands: - - echo "Dummy step" - agents: - queue: "testing" diff --git a/.dockerignore b/.dockerignore index bbfe5a82c..3d2d744f8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,7 +2,6 @@ Dockerfile Dockerfile.* .*.swp -.git .github .gitignore .gitmodules @@ -18,6 +17,7 @@ evm_loader/rust-evm/gasometer/target evm_loader/rust-evm/runtime/target evm_loader/rust-evm/src/target evm_loader/target +test-ledger .idea venv diff --git a/.github/workflows/deploy.py b/.github/workflows/deploy.py index 6e610467d..40f5a5d7e 100644 --- a/.github/workflows/deploy.py +++ b/.github/workflows/deploy.py @@ -7,6 +7,7 @@ import subprocess import requests import json +import typing as tp from urllib.parse import urlparse from github_api_client import GithubClient @@ -29,12 +30,20 @@ DOCKER_USER = os.environ.get("DHUBU") DOCKER_PASSWORD = os.environ.get("DHUBP") -IMAGE_NAME = 'neonlabsorg/evm_loader' -SOLANA_NODE_VERSION = 'v1.14.20' -SOLANA_BPF_VERSION = 'v1.14.20' +IMAGE_NAME = os.environ.get("IMAGE_NAME", "evm_loader") +RUN_LINK_REPO = os.environ.get("RUN_LINK_REPO") +DOCKERHUB_ORG_NAME = os.environ.get("DOCKERHUB_ORG_NAME") +SOLANA_NODE_VERSION = 'v1.18.18' +SOLANA_BPF_VERSION = 'v1.18.18' VERSION_BRANCH_TEMPLATE = r"[vt]{1}\d{1,2}\.\d{1,2}\.x.*" +RELEASE_TAG_TEMPLATE = r"[vt]{1}\d{1,2}\.\d{1,2}\.\d{1,2}" + docker_client = docker.APIClient() +NEON_TEST_IMAGE_NAME = "neon_tests" + +PROXY_ENDPOINT = os.environ.get("PROXY_ENDPOINT") +NEON_TESTS_ENDPOINT = os.environ.get("NEON_TESTS_ENDPOINT") @click.group() @@ -42,54 +51,113 @@ def cli(): pass +def ref_to_image_tag(ref): + return ref.split('/')[-1] + + +def set_github_env(envs: tp.Dict, upper=True) -> None: + """Set environment for github action""" + path = os.getenv("GITHUB_ENV", str()) + if os.path.exists(path): + print(f"Set environment variables: {envs}") + with open(path, "a") as env_file: + for key, value in envs.items(): + env_file.write(f"\n{key.upper() if upper else key}={str(value)}") + + +def is_image_exist(image, tag): + response = requests.get( + url=f"https://registry.hub.docker.com/v2/repositories/{DOCKERHUB_ORG_NAME}/{image}/tags/{tag}") + return response.status_code == 200 + + +@cli.command(name="specify_image_tags") +@click.option('--git_ref') +@click.option('--git_head_ref') +@click.option('--git_base_ref') +def specify_image_tags(git_ref, + git_head_ref, + git_base_ref): + # evm_tag + if "refs/pull" in git_ref: + evm_tag = ref_to_image_tag(git_head_ref) + elif git_ref == "refs/heads/develop": + evm_tag = "latest" + else: + evm_tag = ref_to_image_tag(git_ref) + + # evm_pr_version_branch + evm_pr_version_branch = "" + if git_base_ref: + if re.match(VERSION_BRANCH_TEMPLATE, ref_to_image_tag(git_base_ref)) is not None: + evm_pr_version_branch = ref_to_image_tag(git_base_ref) + + # is_evm_release + if "refs/tags/" in git_ref: + is_evm_release = True + else: + is_evm_release = False + + # test_image_tag + if evm_tag and is_image_exist(NEON_TEST_IMAGE_NAME, evm_tag): + neon_test_tag = evm_tag + elif is_evm_release: + neon_test_tag = re.sub(r'\.[0-9]*$', '.x', evm_tag) + if not is_image_exist(NEON_TEST_IMAGE_NAME, neon_test_tag): + raise RuntimeError(f"{NEON_TEST_IMAGE_NAME} image with {neon_test_tag} tag isn't found") + elif evm_pr_version_branch and is_image_exist(NEON_TEST_IMAGE_NAME, evm_pr_version_branch): + neon_test_tag = evm_pr_version_branch + else: + neon_test_tag = "latest" + + env = dict(evm_tag=evm_tag, + evm_pr_version_branch=evm_pr_version_branch, + is_evm_release=is_evm_release, + neon_test_tag=neon_test_tag) + set_github_env(env) + + @cli.command(name="build_docker_image") -@click.option('--github_sha') -def build_docker_image(github_sha): +@click.option('--evm_sha_tag') +def build_docker_image(evm_sha_tag): solana_image = f'solanalabs/solana:{SOLANA_NODE_VERSION}' docker_client.pull(solana_image) - buildargs = {"REVISION": github_sha, + buildargs = {"REVISION": evm_sha_tag, "SOLANA_IMAGE": solana_image, "SOLANA_BPF_VERSION": SOLANA_BPF_VERSION} - tag = f"{IMAGE_NAME}:{github_sha}" + tag = f"{DOCKERHUB_ORG_NAME}/{IMAGE_NAME}:{evm_sha_tag}" click.echo("start build") output = docker_client.build(tag=tag, buildargs=buildargs, path="./", decode=True) process_output(output) @cli.command(name="publish_image") -@click.option('--github_sha') -def publish_image(github_sha): - docker_client.login(username=DOCKER_USER, password=DOCKER_PASSWORD) - out = docker_client.push(f"{IMAGE_NAME}:{github_sha}", decode=True, stream=True) - process_output(out) +@click.option('--evm_sha_tag') +@click.option('--evm_tag') +def publish_image(evm_sha_tag, evm_tag): + push_image_with_tag(evm_sha_tag, evm_sha_tag) + # push latest and version tags only on the finalizing step + if evm_tag != "latest" and re.match(RELEASE_TAG_TEMPLATE, evm_tag) is None: + push_image_with_tag(evm_sha_tag, evm_tag) @cli.command(name="finalize_image") -@click.option('--head_ref_branch') -@click.option('--github_ref') -@click.option('--github_sha') -def finalize_image(head_ref_branch, github_ref, github_sha): - branch = github_ref.replace("refs/heads/", "") - if re.match(VERSION_BRANCH_TEMPLATE, branch) is None: - if 'refs/tags/' in branch: - tag = branch.replace("refs/tags/", "") - elif branch == 'master': - tag = 'stable' - elif branch == 'develop': - tag = 'latest' - else: - tag = head_ref_branch.split('/')[-1] - - docker_client.login(username=DOCKER_USER, password=DOCKER_PASSWORD) - out = docker_client.pull(f"{IMAGE_NAME}:{github_sha}", decode=True, stream=True) - process_output(out) - - docker_client.tag(f"{IMAGE_NAME}:{github_sha}", f"{IMAGE_NAME}:{tag}") - out = docker_client.push(f"{IMAGE_NAME}:{tag}", decode=True, stream=True) - process_output(out) +@click.option('--evm_sha_tag') +@click.option('--evm_tag') +def finalize_image(evm_sha_tag, evm_tag): + if re.match(RELEASE_TAG_TEMPLATE, evm_tag) is not None or evm_tag == "latest": + push_image_with_tag(evm_sha_tag, evm_tag) else: - click.echo("The image is not published, please create tag for publishing") + click.echo(f"Nothing to finalize, the tag {evm_tag} is not version tag or latest") + + +def push_image_with_tag(sha, tag): + image = f"{DOCKERHUB_ORG_NAME}/{IMAGE_NAME}" + docker_client.login(username=DOCKER_USER, password=DOCKER_PASSWORD) + docker_client.tag(f"{image}:{sha}", f"{image}:{tag}") + out = docker_client.push(f"{image}:{tag}", decode=True, stream=True) + process_output(out) def run_subprocess(command): @@ -98,18 +166,24 @@ def run_subprocess(command): @cli.command(name="run_tests") -@click.option('--github_sha') -def run_tests(github_sha): - image_name = f"{IMAGE_NAME}:{github_sha}" - os.environ["EVM_LOADER_IMAGE"] = image_name - project_name = f"neon-evm-{github_sha}" +@click.option('--evm_sha_tag') +@click.option('--neon_test_tag') +@click.option('--run_number', default=1) +@click.option('--run_attempt', default=1) +def run_tests(evm_sha_tag, neon_test_tag, run_number, run_attempt): + os.environ["EVM_LOADER_IMAGE"] = f"{DOCKERHUB_ORG_NAME}/{IMAGE_NAME}:{evm_sha_tag}" + os.environ["NEON_TESTS_IMAGE"] = f"{DOCKERHUB_ORG_NAME}/{NEON_TEST_IMAGE_NAME}:{neon_test_tag}" + project_name = f"neon-evm-{evm_sha_tag}-{run_number}-{run_attempt}" stop_containers(project_name) - run_subprocess(f"docker-compose -p {project_name} -f ./evm_loader/docker-compose-ci.yml up -d") - container_name = get_solana_container_name(project_name) + run_subprocess(f"docker-compose -p {project_name} -f ./ci/docker-compose-ci.yml pull") + run_subprocess(f"docker-compose -p {project_name} -f ./ci/docker-compose-ci.yml up -d") + test_container_name = get_container_name(project_name, "tests") + click.echo("Start tests") + print(test_container_name) exec_id = docker_client.exec_create( - container=container_name, cmd="/opt/deploy-test.sh") + container=test_container_name, cmd="python3 clickfile.py run evm --numprocesses 8 --network docker_net") logs = docker_client.exec_start(exec_id['Id'], stream=True) tests_are_failed = False @@ -118,77 +192,78 @@ def run_tests(github_sha): current_line = line.decode('utf-8') all_logs += current_line click.echo(current_line) - if 'ERROR ' in current_line or 'FAILED ' in current_line: + if 'ERROR ' in current_line or 'FAILED ' in current_line or 'Error: ' in current_line: tests_are_failed = True print("Tests are failed") - if "[100%]" not in all_logs: - tests_are_failed = True - print("Part of tests are skipped") exec_status = docker_client.exec_inspect(exec_id['Id'])["ExitCode"] + + run_subprocess(f"docker-compose -p {project_name} -f ./ci/docker-compose-ci.yml logs neon-core-api") + stop_containers(project_name) if tests_are_failed or exec_status == 1: sys.exit(1) -def get_solana_container_name(project_name): +def get_container_name(project_name, service_name): data = subprocess.run( - f"docker-compose -p {project_name} -f ./evm_loader/docker-compose-ci.yml ps", + f"docker-compose -p {project_name} -f ./ci/docker-compose-ci.yml ps", shell=True, capture_output=True, text=True).stdout click.echo(data) - pattern = rf'{project_name}_[a-zA-Z0-9_]+' - + pattern = rf'{project_name}[-_]{service_name}[-_]1' match = re.search(pattern, data) return match.group(0) def stop_containers(project_name): - run_subprocess(f"docker-compose -p {project_name} -f ./evm_loader/docker-compose-ci.yml down") + run_subprocess(f"docker-compose -p {project_name} -f ./ci/docker-compose-ci.yml down") @cli.command(name="trigger_proxy_action") -@click.option('--head_ref_branch') -@click.option('--base_ref_branch') -@click.option('--github_ref') -@click.option('--github_sha') +@click.option('--evm_pr_version_branch') +@click.option('--is_evm_release') +@click.option('--evm_sha_tag') +@click.option('--evm_tag') @click.option('--token') -@click.option('--is_draft') @click.option('--labels') -def trigger_proxy_action(head_ref_branch, base_ref_branch, github_ref, github_sha, token, is_draft, labels): - is_develop_branch = github_ref in ['refs/heads/develop', 'refs/heads/master'] - is_tag_creating = 'refs/tags/' in github_ref - is_version_branch = re.match(VERSION_BRANCH_TEMPLATE, github_ref.replace("refs/heads/", "")) is not None - is_FTS_labeled_not_draft = 'FullTestSuit' in labels and is_draft != "true" - - if is_develop_branch or is_tag_creating or is_version_branch or is_FTS_labeled_not_draft: - full_test_suite = "true" +@click.option('--pr_url') +@click.option('--pr_number') +def trigger_proxy_action(evm_pr_version_branch, is_evm_release, evm_sha_tag, evm_tag, token, labels, + pr_url, pr_number): + is_version_branch = re.match(VERSION_BRANCH_TEMPLATE, evm_tag) is not None + is_FTS_labeled = 'fullTestSuit' in labels + + if evm_tag == "latest" or is_evm_release == 'True' or is_version_branch or is_FTS_labeled: + full_test_suite = True else: - full_test_suite = "false" + full_test_suite = False github = GithubClient(token) - if head_ref_branch in github.get_proxy_branches(): - proxy_branch = head_ref_branch - elif re.match(VERSION_BRANCH_TEMPLATE, base_ref_branch): - proxy_branch = base_ref_branch - elif is_tag_creating: - neon_evm_tag = github_ref.replace("refs/tags/", "") - proxy_branch = re.sub(r'\.\d+$', '.x', neon_evm_tag) + # get proxy branch by evm_tag + if GithubClient.is_branch_exist(PROXY_ENDPOINT, evm_tag): + proxy_branch = evm_tag + elif evm_pr_version_branch: + proxy_branch = evm_pr_version_branch + elif is_evm_release == 'True': + proxy_branch = re.sub(r'\.\d+$', '.x', evm_tag) elif is_version_branch: - proxy_branch = github_ref.replace("refs/heads/", "") + proxy_branch = evm_tag else: proxy_branch = 'develop' click.echo(f"Proxy branch: {proxy_branch}") + initial_pr = f"{pr_url}/{pr_number}/comments" if pr_number else "" + runs_before = github.get_proxy_runs_list(proxy_branch) runs_count_before = github.get_proxy_runs_count(proxy_branch) - github.run_proxy_dispatches(proxy_branch, github_ref, github_sha, full_test_suite) + github.run_proxy_dispatches(proxy_branch, evm_tag, evm_sha_tag, evm_pr_version_branch, full_test_suite, initial_pr) wait_condition(lambda: github.get_proxy_runs_count(proxy_branch) > runs_count_before) runs_after = github.get_proxy_runs_list(proxy_branch) proxy_run_id = list(set(runs_after) - set(runs_before))[0] - link = f"https://github.com/neonlabsorg/proxy-model.py/actions/runs/{proxy_run_id}" + link = f"https://github.com/{RUN_LINK_REPO}/actions/runs/{proxy_run_id}" click.echo(f"Proxy run link: {link}") click.echo("Waiting for completed status...") wait_condition(lambda: github.get_proxy_run_info(proxy_run_id)["status"] == "completed", timeout_sec=10800, delay=5) @@ -213,20 +288,27 @@ def wait_condition(func_cond, timeout_sec=60, delay=0.5): @cli.command(name="send_notification", help="Send notification to slack") -@click.option("-u", "--url", help="slack app endpoint url.") -@click.option("-b", "--build_url", help="github action test build url.") -def send_notification(url, build_url): - tpl = ERR_MSG_TPL.copy() - - parsed_build_url = urlparse(build_url).path.split("/") - build_id = parsed_build_url[-1] - repo_name = f"{parsed_build_url[1]}/{parsed_build_url[2]}" - - tpl["blocks"][0]["text"]["text"] = ( - f"*Build <{build_url}|`{build_id}`> of repository `{repo_name}` is failed.*" - f"\n<{build_url}|View build details>" - ) - requests.post(url=url, data=json.dumps(tpl)) +@click.option("--evm_tag", help="slack app endpoint url.") +@click.option("--url", help="slack app endpoint url.") +@click.option("--build_url", help="github action test build url.") +def send_notification(evm_tag, url, build_url): + + if re.match(RELEASE_TAG_TEMPLATE, evm_tag) is not None \ + or re.match(VERSION_BRANCH_TEMPLATE, evm_tag) is not None \ + or evm_tag == "latest": + tpl = ERR_MSG_TPL.copy() + + parsed_build_url = urlparse(build_url).path.split("/") + build_id = parsed_build_url[-1] + repo_name = f"{parsed_build_url[1]}/{parsed_build_url[2]}" + + tpl["blocks"][0]["text"]["text"] = ( + f"*Build <{build_url}|`{build_id}`> of repository `{repo_name}` is failed.*" + f"\n<{build_url}|View build details>" + ) + requests.post(url=url, data=json.dumps(tpl)) + else: + click.echo(f"Notification is not sent, the tag {evm_tag} is not version tag or latest") def process_output(output): diff --git a/.github/workflows/github_api_client.py b/.github/workflows/github_api_client.py index af0039747..3787847ec 100644 --- a/.github/workflows/github_api_client.py +++ b/.github/workflows/github_api_client.py @@ -1,17 +1,18 @@ import click import requests +import os class GithubClient(): - PROXY_ENDPOINT = "https://api.github.com/repos/neonlabsorg/proxy-model.py" def __init__(self, token): + self.proxy_endpoint = os.environ.get("PROXY_ENDPOINT") self.headers = {"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"} def get_proxy_runs_list(self, proxy_branch): response = requests.get( - f"{self.PROXY_ENDPOINT}/actions/workflows/pipeline.yml/runs?branch={proxy_branch}", headers=self.headers) + f"{self.proxy_endpoint}/actions/workflows/pipeline.yml/runs?branch={proxy_branch}", headers=self.headers) if response.status_code != 200: raise RuntimeError(f"Can't get proxy runs list. Error: {response.json()}") runs = [item['id'] for item in response.json()['workflow_runs']] @@ -19,28 +20,36 @@ def get_proxy_runs_list(self, proxy_branch): def get_proxy_runs_count(self, proxy_branch): response = requests.get( - f"{self.PROXY_ENDPOINT}/actions/workflows/pipeline.yml/runs?branch={proxy_branch}", headers=self.headers) + f"{self.proxy_endpoint}/actions/workflows/pipeline.yml/runs?branch={proxy_branch}", headers=self.headers) return int(response.json()["total_count"]) - def run_proxy_dispatches(self, proxy_branch, github_ref, github_sha, full_test_suite): + def run_proxy_dispatches(self, proxy_branch, evm_tag, evm_sha_tag, evm_pr_version_branch, full_test_suite, + initial_pr): data = {"ref": proxy_branch, - "inputs": {"full_test_suite": full_test_suite, - "neon_evm_commit": github_sha, - "neon_evm_branch": github_ref} + "inputs": {"full_test_suite": f"{full_test_suite}".lower(), + "evm_sha_tag": evm_sha_tag, + "evm_tag": evm_tag, + "evm_pr_version_branch": evm_pr_version_branch, + "initial_pr": initial_pr} } response = requests.post( - f"{self.PROXY_ENDPOINT}/actions/workflows/pipeline.yml/dispatches", json=data, headers=self.headers) + f"{self.proxy_endpoint}/actions/workflows/pipeline.yml/dispatches", json=data, headers=self.headers) click.echo(f"Sent data: {data}") click.echo(f"Status code: {response.status_code}") if response.status_code != 204: - raise RuntimeError("proxy-model.py action is not triggered") - - def get_proxy_branches(self): - proxy_branches_obj = requests.get( - f"{self.PROXY_ENDPOINT}/branches?per_page=100").json() - return [item["name"] for item in proxy_branches_obj] + raise RuntimeError("Proxy action is not triggered, error: {response.text}") + + @staticmethod + def is_branch_exist(endpoint, branch): + if branch: + response = requests.get(f"{endpoint}/branches/{branch}") + if response.status_code == 200: + click.echo(f"The branch {branch} exist in the {endpoint} repository") + return True + else: + return False def get_proxy_run_info(self, id): response = requests.get( - f"{self.PROXY_ENDPOINT}/actions/runs/{id}", headers=self.headers) + f"{self.proxy_endpoint}/actions/runs/{id}", headers=self.headers) return response.json() diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 1eed8e3c1..ccc528447 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -13,91 +13,111 @@ on: env: DHUBU: ${{secrets.DHUBU}} DHUBP: ${{secrets.DHUBP}} + PROXY_ENDPOINT: "https://api.github.com/repos/neonlabsorg/neon-proxy.py" + NEON_TESTS_ENDPOINT: ${{vars.NEON_TESTS_ENDPOINT}} + DOCKERHUB_ORG_NAME: ${{vars.DOCKERHUB_ORG_NAME}} + RUN_LINK_REPO: "neonlabsorg/neon-proxy.py" BUILD_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - build-neon-evm: - runs-on: build-runner + build-image: + runs-on: neon-evm-1 + outputs: + evm_tag: ${{ steps.tags.outputs.evm_tag }} + evm_sha_tag: ${{ steps.tags.outputs.evm_sha_tag }} + evm_pr_version_branch: ${{ steps.tags.outputs.evm_pr_version_branch }} + is_evm_release: ${{ steps.tags.outputs.is_evm_release }} + neon_test_tag: ${{ steps.tags.outputs.neon_test_tag }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - + - name: Specify image tags + run: | + python3 ./.github/workflows/deploy.py specify_image_tags \ + --git_ref=${{ github.ref }} \ + --git_head_ref=${{ github.head_ref }} \ + --git_base_ref=${{ github.base_ref }} + - name: Set outputs + id: tags + run: | + echo "evm_tag=${{ env.EVM_TAG }}" >> "$GITHUB_OUTPUT" + echo "evm_sha_tag=${{ github.sha }}" >> "$GITHUB_OUTPUT" + echo "evm_pr_version_branch=${{ env.PROXY_PR_VERSION_BRANCH }}" >> "$GITHUB_OUTPUT" + echo "is_evm_release=${{ env.IS_EVM_RELEASE }}" >> "$GITHUB_OUTPUT" + echo "neon_test_tag=${{ env.NEON_TEST_TAG }}" >> "$GITHUB_OUTPUT" - name: build docker image run: | python3 ./.github/workflows/deploy.py build_docker_image \ - --github_sha=${GITHUB_SHA} - + --evm_sha_tag=${{ steps.tags.outputs.evm_sha_tag }} - name: publish image run: | python3 ./.github/workflows/deploy.py publish_image \ - --github_sha=${GITHUB_SHA} - run-neon-evm-tests: + --evm_sha_tag=${{ steps.tags.outputs.evm_sha_tag }} \ + --evm_tag=${{ steps.tags.outputs.evm_tag }} + run-evm-tests: runs-on: test-runner needs: - - build-neon-evm + - build-image steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Run tests run: | python3 ./.github/workflows/deploy.py run_tests \ - --github_sha=${GITHUB_SHA} + --evm_sha_tag=${{ needs.build-image.outputs.evm_sha_tag }} \ + --neon_test_tag=${{ needs.build-image.outputs.neon_test_tag }} \ + --run_number=${{ github.run_number }} \ + --run_attempt=${{ github.run_attempt }} + - name: Cancel the build if job failed + if: "failure()" + uses: "andymckay/cancel-action@0.4" trigger-proxy-tests: runs-on: trigger-runner needs: - - build-neon-evm + - build-image steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Trigger proxy build run: | python3 ./.github/workflows/deploy.py trigger_proxy_action \ - --github_sha=${GITHUB_SHA} \ - --head_ref_branch=${{ github.head_ref }} \ - --base_ref_branch=${{ github.base_ref }} \ - --github_ref=${{ github.ref }} \ + --evm_pr_version_branch=${{ needs.build-image.outputs.evm_pr_version_branch }} \ + --is_evm_release=${{ needs.build-image.outputs.is_evm_release }} \ + --evm_sha_tag=${{ needs.build-image.outputs.evm_sha_tag }} \ + --evm_tag=${{ needs.build-image.outputs.evm_tag }} \ --token=${{secrets.GHTOKEN }} \ - --is_draft=${{github.event.pull_request.draft}} \ - --labels='${{ toJson(github.event.pull_request.labels.*.name) }}' + --labels='${{ toJson(github.event.pull_request.labels.*.name) }}' \ + --pr_url="${{ github.api_url }}/repos/${{ github.repository }}/issues" \ + --pr_number="${{ github.event.pull_request.number }}" + - name: Cancel the build if job failed + if: "failure()" + uses: "andymckay/cancel-action@0.4" finalize-image: - runs-on: build-runner + runs-on: neon-evm-1 needs: + - build-image - trigger-proxy-tests - - run-neon-evm-tests + - run-evm-tests steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Finalize image run: | python3 ./.github/workflows/deploy.py finalize_image \ - --head_ref_branch=${{ github.head_ref }} \ - --github_ref=${GITHUB_REF} \ - --github_sha=${GITHUB_SHA} - - name: Check if it version branch - id: is_version_branch - run: | - if [[ "${{ github.ref }}" =~ "refs/heads/"[vt][0-9]+\.[0-9]+\.x ]]; then - echo "value=true" - echo "value=true" >> $GITHUB_OUTPUT - else - echo "value=false" - echo "value=false" >> $GITHUB_OUTPUT - fi + --evm_sha_tag=${{ needs.build-image.outputs.evm_sha_tag }} \ + --evm_tag=${{ needs.build-image.outputs.evm_tag }} + - name: Send notification to slack - if: | - failure() && - (github.ref_name == 'develop' || - github.ref_name == 'master' || - steps.is_version_branch.outputs.value) || - startsWith(github.ref , 'refs/tags/') + if: failure() run: | python3 ./.github/workflows/deploy.py send_notification \ + --evm_tag=${{ needs.build-image.outputs.evm_tag }} \ --url=${{secrets.SLACK_EVM_CHANNEL_URL}} \ --build_url=${BUILD_URL} diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt new file mode 100644 index 000000000..1a3655d55 --- /dev/null +++ b/.github/workflows/requirements.txt @@ -0,0 +1,3 @@ +docker==7.1.0 +requests==2.31.0 +click==8.1.3 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 75aec949c..cc74be73e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ target .env .vscode +.trunk bin config.json node_modules @@ -14,3 +15,4 @@ hfuzz_workspace *.code-workspace evm_loader/solidity/artifacts evm_loader/solidity/cache +test-ledger diff --git a/Dockerfile b/Dockerfile index 27d90adfe..b82ac9665 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,98 +1,67 @@ ARG SOLANA_IMAGE # Install BPF SDK -FROM solanalabs/rust:1.64.0 AS builder +FROM solanalabs/rust:1.75.0 AS builder RUN cargo install rustfilt WORKDIR /opt ARG SOLANA_BPF_VERSION RUN sh -c "$(curl -sSfL https://release.solana.com/"${SOLANA_BPF_VERSION}"/install)" && \ - /root/.local/share/solana/install/active_release/bin/sdk/bpf/scripts/install.sh -ENV PATH=/root/.local/share/solana/install/active_release/bin:/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + /root/.local/share/solana/install/active_release/bin/sdk/sbf/scripts/install.sh +ENV PATH=${PATH}:/root/.local/share/solana/install/active_release/bin # Build evm_loader FROM builder AS evm-loader-builder -COPY ./evm_loader/ /opt/evm_loader/ -WORKDIR /opt/evm_loader +COPY .git /opt/neon-evm/.git +COPY evm_loader /opt/neon-evm/evm_loader +WORKDIR /opt/neon-evm/evm_loader ARG REVISION ENV NEON_REVISION=${REVISION} RUN cargo fmt --check && \ cargo clippy --release && \ cargo build --release && \ - cargo build-sbf --arch bpf --features devnet && cp target/deploy/evm_loader.so target/deploy/evm_loader-devnet.so && \ - cargo build-sbf --arch bpf --features testnet && cp target/deploy/evm_loader.so target/deploy/evm_loader-testnet.so && \ - cargo build-sbf --arch bpf --features govertest && cp target/deploy/evm_loader.so target/deploy/evm_loader-govertest.so && \ - cargo build-sbf --arch bpf --features govertest,emergency && cp target/deploy/evm_loader.so target/deploy/evm_loader-govertest-emergency.so && \ - cargo build-sbf --arch bpf --features mainnet && cp target/deploy/evm_loader.so target/deploy/evm_loader-mainnet.so && \ - cargo build-sbf --arch bpf --features mainnet,emergency && cp target/deploy/evm_loader.so target/deploy/evm_loader-mainnet-emergency.so && \ - cargo build-sbf --arch bpf --features ci --dump + cargo test --release && \ + cargo build-sbf --manifest-path program/Cargo.toml --features devnet && cp target/deploy/evm_loader.so target/deploy/evm_loader-devnet.so && \ + cargo build-sbf --manifest-path program/Cargo.toml --features testnet && cp target/deploy/evm_loader.so target/deploy/evm_loader-testnet.so && \ + cargo build-sbf --manifest-path program/Cargo.toml --features govertest && cp target/deploy/evm_loader.so target/deploy/evm_loader-govertest.so && \ + cargo build-sbf --manifest-path program/Cargo.toml --features govertest,emergency && cp target/deploy/evm_loader.so target/deploy/evm_loader-govertest-emergency.so && \ + cargo build-sbf --manifest-path program/Cargo.toml --features mainnet && cp target/deploy/evm_loader.so target/deploy/evm_loader-mainnet.so && \ + cargo build-sbf --manifest-path program/Cargo.toml --features mainnet,emergency && cp target/deploy/evm_loader.so target/deploy/evm_loader-mainnet-emergency.so && \ + cargo build-sbf --manifest-path program/Cargo.toml --features rollup && cp target/deploy/evm_loader.so target/deploy/evm_loader-rollup.so && \ + cargo build-sbf --manifest-path program/Cargo.toml --features ci --dump -# Build Solidity contracts -FROM ethereum/solc:0.8.0 AS solc -FROM ubuntu:20.04 AS contracts -RUN apt-get update && \ - DEBIAN_FRONTEND=nontineractive apt-get -y install xxd && \ - rm -rf /var/lib/apt/lists/* /var/lib/apt/cache/* -COPY evm_loader/tests/contracts/*.sol /opt/ -COPY evm_loader/solidity/*.sol /opt/ -#COPY evm_loader/tests/test_solidity_precompiles.json /opt/ -COPY --from=solc /usr/bin/solc /usr/bin/solc -WORKDIR /opt/ -RUN solc --optimize --optimize-runs 200 --output-dir . --bin *.sol && \ - for file in $(ls *.bin); do xxd -r -p $file >${file}ary; done && \ - ls -l -# Define solana-image that contains utility -FROM ${SOLANA_IMAGE} AS solana - -# Build target image -FROM ubuntu:20.04 AS base -WORKDIR /opt -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get -y install vim less openssl ca-certificates curl python3 python3-pip parallel && \ - rm -rf /var/lib/apt/lists/* +# Add neon_test_invoke_program to the genesis +FROM neonlabsorg/neon_test_programs:latest AS neon_test_programs -COPY evm_loader/tests/requirements.txt /tmp/ -RUN pip3 install -r /tmp/requirements.txt - -COPY /evm_loader/solidity/ /opt/contracts/contracts/ -WORKDIR /opt +# Define solana-image that contains utility +FROM builder AS base -COPY --from=solana \ - /usr/bin/solana \ - /usr/bin/solana-validator \ - /usr/bin/solana-keygen \ - /usr/bin/solana-faucet \ - /usr/bin/solana-genesis \ - /usr/bin/solana-run.sh \ - /usr/bin/fetch-spl.sh \ - /usr/bin/spl* \ - /opt/solana/bin/ +RUN solana program dump metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s /opt/metaplex.so --url mainnet-beta -RUN /opt/solana/bin/solana program dump metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s /opt/solana/bin/metaplex.so --url mainnet-beta +COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/deploy/evm_loader*.so /opt/ +COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/deploy/evm_loader-dump.txt /opt/ +COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/release/neon-cli /opt/ +COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/release/neon-api /opt/ -COPY evm_loader/solana-run-neon.sh \ - /opt/solana/bin/ +COPY --from=neon_test_programs /opt/deploy/ /opt/deploy/ +COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/release/neon-rpc /opt/ +COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/release/libneon_lib.so /opt/libs/current/ -COPY --from=evm-loader-builder /opt/evm_loader/target/deploy/evm_loader*.so /opt/ -COPY --from=evm-loader-builder /opt/evm_loader/target/deploy/evm_loader-dump.txt /opt/ -COPY --from=evm-loader-builder /opt/evm_loader/target/release/neon-cli /opt/ -COPY --from=evm-loader-builder /opt/evm_loader/target/release/neon-api /opt/ -COPY --from=solana /usr/bin/spl-token /opt/spl-token -COPY --from=contracts /opt/ /opt/solidity/ -COPY --from=contracts /usr/bin/solc /usr/bin/solc -COPY evm_loader/wait-for-solana.sh \ - evm_loader/wait-for-neon.sh \ - evm_loader/create-test-accounts.sh \ - evm_loader/deploy-evm.sh \ - evm_loader/deploy-test.sh \ - evm_loader/evm_loader-keypair.json \ +COPY ci/wait-for-solana.sh \ + ci/wait-for-neon.sh \ + ci/solana-run-neon.sh \ + ci/deploy-evm.sh \ + ci/deploy-multi-tokens.sh \ + ci/create-test-accounts.sh \ + ci/evm_loader-keypair.json \ /opt/ -COPY evm_loader/keys/ /opt/keys -COPY evm_loader/tests /opt/tests -COPY evm_loader/operator1-keypair.json /root/.config/solana/id.json -COPY evm_loader/operator2-keypair.json /root/.config/solana/id2.json +COPY solidity/ /opt/solidity +COPY ci/operator-keypairs/ /opt/operator-keypairs +COPY ci/operator-keypairs/id.json /root/.config/solana/id.json +COPY ci/operator-keypairs/id2.json /root/.config/solana/id2.json +COPY ci/keys/ /opt/keys +ENV PATH=${PATH}:/opt -ENV CONTRACTS_DIR=/opt/solidity/ -ENV PATH=/opt/solana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt +ENTRYPOINT [ "/opt/solana-run-neon.sh" ] diff --git a/LICENSE b/LICENSE index d9db7a3c4..b0220600a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,92 +1,19 @@ -Business Source License 1.1 +Neon EVM License -License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. -"Business Source License" is a trademark of MariaDB Corporation Ab. - ------------------------------------------------------------------------------ - -Parameters - -Licensor: Neon Labs - -Licensed Work: Neon EVM - The Licensed Work is (c) 2021 Neon Labs - ------------------------------------------------------------------------------ +Copyright (c) 2023 by Neon Protocol Ltd. +All rights reserved. Terms - -The Licensor hereby grants you the right to copy, modify, create derivative -works, redistribute, and make non-production use of the Licensed Work. The -Licensor may make an Additional Use Grant, above, permitting limited -production use. - -Effective on the Change Date, or the fourth anniversary of the first publicly -available distribution of a specific version of the Licensed Work under this -License, whichever comes first, the Licensor hereby grants you rights under -the terms of the Change License, and the rights granted in the paragraph -above terminate. - -If your use of the Licensed Work does not comply with the requirements -currently in effect as described in this License, you must purchase a -commercial license from the Licensor, its affiliated entities, or authorized -resellers, or you must refrain from using the Licensed Work. - -All copies of the original and modified Licensed Work, and derivative works -of the Licensed Work, are subject to this License. This License applies -separately for each version of the Licensed Work and the Change Date may vary -for each version of the Licensed Work released by Licensor. - -You must conspicuously display this License on each original or modified copy -of the Licensed Work. If you receive the Licensed Work in original or -modified form from a third party, the terms and conditions set forth in this -License apply to your use of that work. - -Any use of the Licensed Work in violation of this License will automatically -terminate your rights under this License for the current and all other -versions of the Licensed Work. - -This License does not grant you any right in any trademark or logo of -Licensor or its affiliates (provided that you may use a trademark or logo of -Licensor as expressly required by this License). - -TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON -AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, -EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND -TITLE. - -MariaDB hereby grants you permission to use this License’s text to license -your works, and to refer to it using the trademark "Business Source License", -as long as you comply with the Covenants of Licensor below. - ------------------------------------------------------------------------------ - -Covenants of Licensor - -In consideration of the right to use this License’s text and the "Business -Source License" name and trademark, Licensor covenants to MariaDB, and to all -other recipients of the licensed work to be provided by Licensor: - -1. To specify as the Change License the GPL Version 2.0 or any later version, - or a license that is compatible with GPL Version 2.0 or a later version, - where "compatible" means that software provided under the Change License can - be included in a program with software provided under GPL Version 2.0 or a - later version. Licensor may specify additional Change Licenses without - limitation. - -2. To either: (a) specify an additional grant of rights to use that does not - impose any additional restriction on the right granted in this License, as - the Additional Use Grant; or (b) insert the text "None". - -3. To specify a Change Date. - -4. Not to modify this License in any other way. - ------------------------------------------------------------------------------ - -Notice - -The Business Source License (this document, or the "License") is not an Open -Source license. However, the Licensed Work will eventually be made available -under an Open Source License, as stated in this License. + 1. Grant of license: non-commercial usage for information purposes only, without the right to copy or compile. + 2. Restrictions: The code, in whole or in part (“Licensed Work”), may not be copied, reproduced, distributed, modified, or forked without explicit prior written permission from Neon Protocol Ltd. + 3. No Redistribution: Redistribution of the source code or any derivative work is strictly prohibited. If you wish to use in any way or distribute the software, please request permission by contacting legal@neonfoundation.io. + 4. Audit and Enforcement: Neon Protocol Ltd. reserves the right to audit uses of the Licensed Work to ensure compliance with this license. + 5. Legal Recourse for Violation: Violations may result in legal action, including injunctions, damages, and recovery of legal costs. + 6. Notification of Changes: Any modifications under permission must be documented and communicated to Neon Protocol Ltd. + 7. Attribution and Branding: Distributions (if approved by Neon Protocol Ltd) must include attribution to Neon Protocol Ltd. and adhere to specified branding guidelines. + 8. Limitations on Transferability: Rights and permissions under this license are non-transferable and non-sublicensable, unless explicitly allowed by Neon Protocol Ltd. + 9. Acknowledgement: Permitted redistributions must include: “Copyright (c) 2023 by Neon Protocol Ltd”. + 10. License Scope: Applies to all versions of the Licensed Work, including copies, modifications, and derivatives. + 11. Termination for Violation: Any violation terminates your rights for all versions of the Licensed Work. + 12. Trademark Rights: No rights granted in any trademark or logo of the Neon Protocol Ltd, except as required by this License. + 13. Disclaimer: The Licensed Work is provided “AS IS” without any warranties diff --git a/Neon_EVM_Whitepaper.pdf b/Neon_EVM_Whitepaper.pdf new file mode 100644 index 000000000..8148d2972 Binary files /dev/null and b/Neon_EVM_Whitepaper.pdf differ diff --git a/evm_loader/create-test-accounts.sh b/ci/create-test-accounts.sh similarity index 67% rename from evm_loader/create-test-accounts.sh rename to ci/create-test-accounts.sh index 4c1ddcd42..7a2cd511a 100755 --- a/evm_loader/create-test-accounts.sh +++ b/ci/create-test-accounts.sh @@ -34,20 +34,16 @@ function createAccount() { fi ACCOUNT=$(solana address -k "${ID_FILE}") echo "$(date "+%F %X.%3N") I ${FILENAME}:${LINENO} $$ ${COMPONENT}:CreateTestAcc {} New account ${ACCOUNT}" - # if ! solana account "${ACCOUNT}"; then - echo "$(date "+%F %X.%3N") I ${FILENAME}:${LINENO} $$ ${COMPONENT}:CreateTestAcc {} airdropping..." - solana airdrop 5000 "${ACCOUNT}" - # check that balance >= 10 otherwise airdroping by 1 SOL up to 10 + + echo "$(date "+%F %X.%3N") I ${FILENAME}:${LINENO} $$ ${COMPONENT}:CreateTestAcc {} airdropping..." + solana airdrop 5000 "${ACCOUNT}" + # check that balance >= 10 otherwise airdroping by 1 SOL up to 10 + BALANCE=$(solana balance "${ACCOUNT}" | tr '.' '\t'| tr '[:space:]' '\t' | cut -f1) + while [ "${BALANCE}" -lt 10 ]; do + solana airdrop 1 "${ACCOUNT}" + sleep 1 BALANCE=$(solana balance "${ACCOUNT}" | tr '.' '\t'| tr '[:space:]' '\t' | cut -f1) - while [ "${BALANCE}" -lt 10 ]; do - solana airdrop 1 "${ACCOUNT}" - sleep 1 - BALANCE=$(solana balance "${ACCOUNT}" | tr '.' '\t'| tr '[:space:]' '\t' | cut -f1) - done - - TOKEN_ACCOUNT=$(spl-token create-account ${NEON_TOKEN_MINT} --owner ${ACCOUNT} --fee-payer ${ID_FILE} | grep -Po 'Creating account \K[^\n]*' || true) - spl-token mint ${NEON_TOKEN_MINT} 5000 --owner evm_loader-keypair.json --fee-payer ${ID_FILE} -- ${TOKEN_ACCOUNT} - # fi + done } NUM_ACCOUNTS=${1} diff --git a/evm_loader/deploy-evm.sh b/ci/deploy-evm.sh similarity index 73% rename from evm_loader/deploy-evm.sh rename to ci/deploy-evm.sh index f431a1b5b..e51015ddc 100755 --- a/evm_loader/deploy-evm.sh +++ b/ci/deploy-evm.sh @@ -11,7 +11,7 @@ if [ -z "$EVM_LOADER" ]; then exit 1 fi -echo "Deployed from " $(solana address) " with " $(solana balance) +echo "Deploying from " $(solana address) " with " $(solana balance) if [ "$SKIP_EVM_DEPLOY" != "YES" ]; then echo "Deploying evm_loader at address $EVM_LOADER..." if ! solana program deploy --url $SOLANA_URL \ @@ -20,6 +20,7 @@ if [ "$SKIP_EVM_DEPLOY" != "YES" ]; then echo "Failed to deploy evm_loader" exit 1 fi + echo "Deployed evm_loader at address $EVM_LOADER..." sleep 30 else echo "Skip deploying of evm_loader" @@ -28,4 +29,5 @@ fi echo "Deployed finished from " $(solana address) " with " $(solana balance) neon-cli --url $SOLANA_URL --evm_loader $EVM_LOADER \ --keypair evm_loader-keypair.json \ - --loglevel warn init-environment --send-trx --keys-dir keys/ \ No newline at end of file + --solana_key_for_config BMp6gEnveANdvSvspESJUrNczuHz1GF5UQKjVLCkAZih \ + --loglevel debug init-environment --send-trx --keys-dir keys/ diff --git a/ci/deploy-multi-tokens.sh b/ci/deploy-multi-tokens.sh new file mode 100755 index 000000000..bb2f9db96 --- /dev/null +++ b/ci/deploy-multi-tokens.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -em + +echo "Deploy USDT token" + +USDT_ADDRESS=$(solana address -k /opt/keys/usdt_token_keypair.json) + +spl-token -u http://localhost:8899 create-token --decimals 6 /opt/keys/usdt_token_keypair.json +spl-token -u http://localhost:8899 create-account $USDT_ADDRESS +spl-token -u http://localhost:8899 mint $USDT_ADDRESS 100000000000 + + +echo "Deploy ETH token" + +ETH_ADDRESS=$(solana address -k /opt/keys/eth_token_keypair.json) + +spl-token -u http://localhost:8899 create-token --decimals 8 /opt/keys/eth_token_keypair.json +spl-token -u http://localhost:8899 create-account $ETH_ADDRESS +spl-token -u http://localhost:8899 mint $ETH_ADDRESS 100000000000 \ No newline at end of file diff --git a/ci/docker-compose-ci.yml b/ci/docker-compose-ci.yml new file mode 100644 index 000000000..1404d0992 --- /dev/null +++ b/ci/docker-compose-ci.yml @@ -0,0 +1,91 @@ +version: "2.1" + +services: + solana: + image: ${EVM_LOADER_IMAGE} + environment: + - SOLANA_URL=http://127.0.0.1:8899 + - CI=true + hostname: solana + ports: + - "8899" + expose: + - "8899" + ulimits: + nofile: + soft: 1048576 + hard: 1048576 + entrypoint: + /opt/solana-run-neon.sh + networks: + - net + + neon-core-api: + restart: unless-stopped + hostname: neon_api + entrypoint: + /opt/neon-api -H 0.0.0.0:8085 + environment: + RUST_BACKTRACE: 1 + RUST_LOG: debug + NEON_API_LISTENER_ADDR: 0.0.0.0:8085 + SOLANA_URL: http://solana:8899 + EVM_LOADER: 53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io + # operator-keypairs/id.json + SOLANA_KEY_FOR_CONFIG: BMp6gEnveANdvSvspESJUrNczuHz1GF5UQKjVLCkAZih + COMMITMENT: confirmed + NEON_DB_CLICKHOUSE_URLS: "http://45.250.253.36:8123;http://45.250.253.38:8123" + TRACER_DB_TYPE: clickhouse + image: ${EVM_LOADER_IMAGE} + ports: + - "8085" + expose: + - "8085" + networks: + - net + + neon-core-rpc: + restart: unless-stopped + hostname: neon_core_rpc + entrypoint: /opt/neon-rpc /opt/libs/current + environment: + RUST_BACKTRACE: full + RUST_LOG: neon=debug + NEON_API_LISTENER_ADDR: 0.0.0.0:3100 + SOLANA_URL: http://solana:8899 + EVM_LOADER: 53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io + NEON_TOKEN_MINT: HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU + NEON_CHAIN_ID: 111 + COMMITMENT: confirmed + NEON_DB_CLICKHOUSE_URLS: "http://45.250.253.36:8123;http://45.250.253.38:8123" + TRACER_DB_TYPE: clickhouse + NEON_DB_INDEXER_HOST: 45.250.253.32 + NEON_DB_INDEXER_PORT: 5432 + NEON_DB_INDEXER_DATABASE: indexer + NEON_DB_INDEXER_USER: postgres + NEON_DB_INDEXER_PASSWORD: "vUlpDyAP0gA98R5Bu" + KEYPAIR: /opt/operator-keypairs/id.json + FEEPAIR: /opt/operator-keypairs/id.json + SOLANA_KEY_FOR_CONFIG: BMp6gEnveANdvSvspESJUrNczuHz1GF5UQKjVLCkAZih + + image: ${EVM_LOADER_IMAGE} + ports: + - "3100" + expose: + - "3100" + networks: + - net + + tests: + image: ${NEON_TESTS_IMAGE} + environment: + - SOLANA_URL=http://solana:8899 + - NEON_CORE_API_URL=http://neon_api:8085/api + - NEON_CORE_API_RPC_URL=http://neon_core_rpc:3100 + hostname: tests + command: sleep infinity + networks: + - net + +networks: + net: diff --git a/ci/docker-compose-test.yml b/ci/docker-compose-test.yml new file mode 100644 index 000000000..925be1da6 --- /dev/null +++ b/ci/docker-compose-test.yml @@ -0,0 +1,92 @@ +version: "2.1" + +services: + solana: + container_name: solana + image: ${EVM_LOADER_IMAGE} + environment: + - SOLANA_URL=http://127.0.0.1:8899 + hostname: solana + ports: + - 8899:8899 + - 9900:9900 + - 8900:8900 + - 8003:8003/udp + expose: + - "8899" + - "9900" + - "8900" + - "8003/udp" + entrypoint: + /opt/solana-run-neon.sh + networks: + - net + + neon-core-api: + container_name: neon-core-api + restart: unless-stopped + hostname: neon_api + entrypoint: + /opt/neon-api -H 0.0.0.0:8085 + environment: + RUST_BACKTRACE: 1 + RUST_LOG: debug + NEON_API_LISTENER_ADDR: 0.0.0.0:8085 + SOLANA_URL: http://solana:8899 + EVM_LOADER: 53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io + # operator-keypairs/id.json + SOLANA_KEY_FOR_CONFIG: BMp6gEnveANdvSvspESJUrNczuHz1GF5UQKjVLCkAZih + COMMITMENT: confirmed + NEON_DB_CLICKHOUSE_URLS: "http://45.250.253.36:8123;http://45.250.253.38:8123" + TRACER_DB_TYPE: clickhouse + image: ${EVM_LOADER_IMAGE} + ports: + - "8085:8085" + expose: + - "8085" + networks: + - net + + neon-core-rpc: + restart: unless-stopped + container_name: neon-core-rpc + hostname: neon_core_rpc + entrypoint: /opt/neon-rpc /opt/libs/current + environment: + RUST_BACKTRACE: full + RUST_LOG: neon=debug + NEON_API_LISTENER_ADDR: 0.0.0.0:3100 + SOLANA_URL: http://solana:8899 + EVM_LOADER: 53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io + NEON_TOKEN_MINT: HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU + NEON_CHAIN_ID: 111 + COMMITMENT: confirmed + NEON_DB_CLICKHOUSE_URLS: "http://45.250.253.36:8123;http://45.250.253.38:8123" + TRACER_DB_TYPE: clickhouse + NEON_DB_INDEXER_HOST: 45.250.253.32 + NEON_DB_INDEXER_PORT: 5432 + NEON_DB_INDEXER_DATABASE: indexer + NEON_DB_INDEXER_USER: postgres + NEON_DB_INDEXER_PASSWORD: "vUlpDyAP0gA98R5Bu" + KEYPAIR: /opt/operator-keypairs/id.json + FEEPAIR: /opt/operator-keypairs/id.json + image: ${EVM_LOADER_IMAGE} + ports: + - "3100:3100" + expose: + - "3100" + networks: + - net + tests: + image: ${NEON_TESTS_IMAGE} + environment: + - SOLANA_URL=http://solana:8899 + - NEON_CORE_API_URL=http://neon_api:8085/api + - NEON_CORE_API_RPC_URL=http://neon_core_rpc:3100 + hostname: tests + command: sleep infinity + networks: + - net + +networks: + net: \ No newline at end of file diff --git a/evm_loader/evm_loader-keypair.json b/ci/evm_loader-keypair.json similarity index 100% rename from evm_loader/evm_loader-keypair.json rename to ci/evm_loader-keypair.json diff --git a/ci/keys/eth_token_keypair.json b/ci/keys/eth_token_keypair.json new file mode 100644 index 000000000..c5ecbf397 --- /dev/null +++ b/ci/keys/eth_token_keypair.json @@ -0,0 +1 @@ +[149,157,253,18,79,248,207,190,171,37,143,234,67,148,65,17,143,170,213,176,108,59,151,135,190,16,136,132,66,83,171,245,207,17,33,212,150,94,60,140,151,21,192,215,8,4,188,12,177,162,122,143,210,29,168,72,15,167,83,22,57,55,216,224] \ No newline at end of file diff --git a/evm_loader/keys/neon_token_keypair.json b/ci/keys/neon_token_keypair.json similarity index 100% rename from evm_loader/keys/neon_token_keypair.json rename to ci/keys/neon_token_keypair.json diff --git a/ci/keys/usdt_token_keypair.json b/ci/keys/usdt_token_keypair.json new file mode 100644 index 000000000..7c3449f27 --- /dev/null +++ b/ci/keys/usdt_token_keypair.json @@ -0,0 +1 @@ +[69,155,233,42,154,80,125,187,83,175,85,62,77,186,55,98,247,62,182,65,77,158,146,42,242,167,167,161,140,231,54,161,24,80,108,149,163,214,41,81,196,6,86,50,237,190,104,217,0,12,98,124,40,30,211,87,149,74,225,39,48,7,131,80] \ No newline at end of file diff --git a/evm_loader/operator1-keypair.json b/ci/operator-keypairs/id.json similarity index 100% rename from evm_loader/operator1-keypair.json rename to ci/operator-keypairs/id.json diff --git a/ci/operator-keypairs/id10.json b/ci/operator-keypairs/id10.json new file mode 100644 index 000000000..0775610ce --- /dev/null +++ b/ci/operator-keypairs/id10.json @@ -0,0 +1 @@ +[95,1,219,168,183,165,35,48,232,59,7,206,250,251,243,138,175,212,58,154,252,147,193,16,240,56,129,24,104,220,21,244,70,237,248,21,84,35,91,157,114,197,241,253,93,33,93,230,14,255,229,131,73,153,27,16,86,172,34,220,16,128,14,26] \ No newline at end of file diff --git a/ci/operator-keypairs/id11.json b/ci/operator-keypairs/id11.json new file mode 100644 index 000000000..db7143029 --- /dev/null +++ b/ci/operator-keypairs/id11.json @@ -0,0 +1 @@ +[112,233,194,170,175,217,98,186,160,118,143,249,88,40,179,77,222,62,121,149,205,155,152,1,34,243,188,44,66,231,63,202,143,44,91,212,211,86,255,54,80,56,164,192,211,49,40,167,211,153,130,207,41,220,90,64,169,45,26,169,42,202,231,252] \ No newline at end of file diff --git a/ci/operator-keypairs/id12.json b/ci/operator-keypairs/id12.json new file mode 100644 index 000000000..3cfb114dc --- /dev/null +++ b/ci/operator-keypairs/id12.json @@ -0,0 +1 @@ +[107,242,59,236,113,145,60,13,84,146,102,174,36,95,255,207,104,129,118,70,114,139,210,54,17,195,176,206,202,153,82,111,25,231,254,200,204,102,17,163,119,42,14,188,149,225,220,221,195,14,163,251,62,228,171,180,124,174,161,152,231,162,221,54] \ No newline at end of file diff --git a/ci/operator-keypairs/id13.json b/ci/operator-keypairs/id13.json new file mode 100644 index 000000000..e5e633ee3 --- /dev/null +++ b/ci/operator-keypairs/id13.json @@ -0,0 +1 @@ +[132,30,178,226,98,41,184,97,30,124,214,218,107,138,88,82,206,12,27,217,25,6,146,118,146,174,17,129,255,96,33,56,204,58,207,78,57,180,19,42,180,42,245,237,110,83,156,150,192,15,157,91,246,49,103,146,168,79,122,156,136,146,247,152] \ No newline at end of file diff --git a/ci/operator-keypairs/id14.json b/ci/operator-keypairs/id14.json new file mode 100644 index 000000000..3258026f3 --- /dev/null +++ b/ci/operator-keypairs/id14.json @@ -0,0 +1 @@ +[77,155,208,78,141,49,104,72,43,252,108,43,240,125,166,156,235,146,195,221,21,235,48,39,224,133,233,174,110,10,211,154,123,240,134,8,166,118,208,116,229,35,39,53,203,206,97,95,22,107,236,62,70,200,191,63,221,34,154,91,159,153,180,171] \ No newline at end of file diff --git a/ci/operator-keypairs/id15.json b/ci/operator-keypairs/id15.json new file mode 100644 index 000000000..317b10c65 --- /dev/null +++ b/ci/operator-keypairs/id15.json @@ -0,0 +1 @@ +[176,43,81,75,52,27,223,223,223,150,148,199,16,151,195,121,143,174,242,189,90,208,164,198,92,28,77,104,72,230,11,154,212,64,129,103,42,57,174,119,63,101,167,173,184,97,146,208,207,208,81,239,29,214,66,101,101,151,247,130,197,17,172,170] \ No newline at end of file diff --git a/ci/operator-keypairs/id16.json b/ci/operator-keypairs/id16.json new file mode 100644 index 000000000..a2ea6fa61 --- /dev/null +++ b/ci/operator-keypairs/id16.json @@ -0,0 +1 @@ +[56,132,45,132,252,89,229,88,99,111,37,90,53,175,128,227,57,228,18,166,56,5,168,23,33,127,37,69,154,150,67,213,227,9,242,48,173,228,249,65,55,107,84,219,87,138,241,192,218,29,149,67,43,239,9,132,93,209,218,167,27,138,112,46] \ No newline at end of file diff --git a/ci/operator-keypairs/id17.json b/ci/operator-keypairs/id17.json new file mode 100644 index 000000000..5cbbbd54d --- /dev/null +++ b/ci/operator-keypairs/id17.json @@ -0,0 +1 @@ +[169,132,11,32,251,137,85,119,180,62,4,36,213,240,247,29,61,194,252,19,134,98,188,82,22,213,206,195,39,133,36,162,202,14,212,69,151,114,40,66,173,213,100,145,235,70,34,247,101,166,227,86,18,81,56,17,58,103,152,200,117,163,88,236] \ No newline at end of file diff --git a/ci/operator-keypairs/id18.json b/ci/operator-keypairs/id18.json new file mode 100644 index 000000000..5a2ad7abc --- /dev/null +++ b/ci/operator-keypairs/id18.json @@ -0,0 +1 @@ +[34,136,232,16,199,6,212,178,9,245,202,238,151,70,70,205,173,92,116,43,176,222,250,193,7,242,79,227,3,252,57,47,171,52,75,200,195,215,71,197,31,185,128,5,62,168,237,61,5,25,229,26,19,201,190,9,149,80,104,95,157,123,207,40] \ No newline at end of file diff --git a/ci/operator-keypairs/id19.json b/ci/operator-keypairs/id19.json new file mode 100644 index 000000000..716b611d6 --- /dev/null +++ b/ci/operator-keypairs/id19.json @@ -0,0 +1 @@ +[107,48,148,246,46,55,52,252,132,177,71,135,119,118,187,16,236,80,237,198,239,54,112,107,225,26,36,200,242,63,40,71,143,117,14,174,182,27,199,155,93,167,196,216,69,150,100,195,216,48,222,143,240,185,186,37,202,137,179,232,239,26,85,242] \ No newline at end of file diff --git a/evm_loader/operator2-keypair.json b/ci/operator-keypairs/id2.json similarity index 100% rename from evm_loader/operator2-keypair.json rename to ci/operator-keypairs/id2.json diff --git a/ci/operator-keypairs/id20.json b/ci/operator-keypairs/id20.json new file mode 100644 index 000000000..03a27c45e --- /dev/null +++ b/ci/operator-keypairs/id20.json @@ -0,0 +1 @@ +[203,226,8,46,184,170,110,207,167,123,46,136,82,48,44,123,212,127,198,109,113,6,103,190,9,144,89,120,215,45,75,17,134,73,9,238,232,115,234,164,179,96,197,67,31,175,86,222,149,202,209,164,83,170,133,179,189,178,99,158,15,3,152,135] \ No newline at end of file diff --git a/ci/operator-keypairs/id21.json b/ci/operator-keypairs/id21.json new file mode 100644 index 000000000..caa4fb733 --- /dev/null +++ b/ci/operator-keypairs/id21.json @@ -0,0 +1 @@ +[218,78,39,37,233,34,133,207,6,61,140,213,98,27,8,7,255,50,100,179,83,154,114,120,52,199,178,29,32,131,193,9,46,69,220,188,192,48,86,70,174,193,58,32,171,172,54,169,134,56,245,54,239,253,235,32,155,211,137,90,163,61,54,57] \ No newline at end of file diff --git a/ci/operator-keypairs/id22.json b/ci/operator-keypairs/id22.json new file mode 100644 index 000000000..7966c4ba4 --- /dev/null +++ b/ci/operator-keypairs/id22.json @@ -0,0 +1 @@ +[213,38,17,156,63,219,208,33,176,13,185,208,204,212,179,20,63,74,116,6,104,176,68,242,100,10,76,138,124,95,99,168,91,253,251,27,103,97,112,214,38,187,136,19,100,149,58,106,184,17,85,214,7,150,172,139,155,133,93,93,168,107,139,174] \ No newline at end of file diff --git a/ci/operator-keypairs/id23.json b/ci/operator-keypairs/id23.json new file mode 100644 index 000000000..e2c659a57 --- /dev/null +++ b/ci/operator-keypairs/id23.json @@ -0,0 +1 @@ +[161,138,142,31,77,141,153,110,193,211,60,52,240,89,17,162,42,125,158,78,243,214,203,144,85,66,34,231,222,87,106,54,184,10,234,92,109,70,63,141,23,221,150,4,181,90,234,216,132,7,213,115,220,202,207,31,31,191,66,27,76,106,139,28] \ No newline at end of file diff --git a/ci/operator-keypairs/id24.json b/ci/operator-keypairs/id24.json new file mode 100644 index 000000000..ab53a2b48 --- /dev/null +++ b/ci/operator-keypairs/id24.json @@ -0,0 +1 @@ +[111,225,67,67,201,129,87,214,46,150,197,209,142,102,243,6,197,103,130,19,12,11,205,90,228,216,232,212,42,87,217,246,20,32,169,144,96,175,129,11,93,237,7,215,62,60,254,34,29,15,133,117,199,162,8,140,144,35,233,179,139,198,54,30] \ No newline at end of file diff --git a/ci/operator-keypairs/id25.json b/ci/operator-keypairs/id25.json new file mode 100644 index 000000000..c3cd09b2a --- /dev/null +++ b/ci/operator-keypairs/id25.json @@ -0,0 +1 @@ +[124,147,121,70,22,245,195,2,113,226,67,159,168,33,247,202,149,42,5,18,192,26,192,41,21,255,15,1,115,38,184,182,28,114,114,195,60,79,241,252,192,204,205,176,152,170,117,102,255,74,145,69,85,136,64,214,177,134,255,221,184,119,141,125] \ No newline at end of file diff --git a/ci/operator-keypairs/id26.json b/ci/operator-keypairs/id26.json new file mode 100644 index 000000000..11980bcaa --- /dev/null +++ b/ci/operator-keypairs/id26.json @@ -0,0 +1 @@ +[194,43,141,149,241,214,135,212,243,123,229,238,68,168,5,2,148,253,171,107,40,251,133,240,237,248,82,44,45,252,177,157,9,157,56,52,149,119,152,127,172,5,132,184,12,65,237,196,211,249,93,75,246,23,49,187,1,196,138,243,254,148,127,200] \ No newline at end of file diff --git a/ci/operator-keypairs/id27.json b/ci/operator-keypairs/id27.json new file mode 100644 index 000000000..00c30dea2 --- /dev/null +++ b/ci/operator-keypairs/id27.json @@ -0,0 +1 @@ +[208,83,61,173,206,206,193,114,39,85,114,190,81,55,97,76,94,156,47,248,193,163,230,202,176,121,89,43,16,210,146,36,208,251,160,86,224,180,79,140,119,14,118,76,60,79,81,96,160,123,179,88,201,27,173,141,126,239,109,213,242,106,36,246] \ No newline at end of file diff --git a/ci/operator-keypairs/id28.json b/ci/operator-keypairs/id28.json new file mode 100644 index 000000000..4b858ba33 --- /dev/null +++ b/ci/operator-keypairs/id28.json @@ -0,0 +1 @@ +[209,78,176,59,205,21,225,147,100,16,206,202,223,36,96,43,85,159,238,98,244,52,0,49,98,111,122,225,225,165,9,112,104,103,35,237,229,14,253,80,210,177,222,83,166,131,106,170,212,72,208,227,125,135,92,255,38,250,183,162,139,32,85,50] \ No newline at end of file diff --git a/ci/operator-keypairs/id29.json b/ci/operator-keypairs/id29.json new file mode 100644 index 000000000..454b40b63 --- /dev/null +++ b/ci/operator-keypairs/id29.json @@ -0,0 +1 @@ +[139,25,69,250,8,210,132,135,49,210,33,168,172,48,73,239,226,151,2,49,128,127,136,222,160,209,112,172,83,153,57,59,231,21,48,207,152,236,233,187,223,100,82,93,7,113,26,194,124,70,245,140,6,215,63,170,178,46,130,201,93,40,215,178] \ No newline at end of file diff --git a/ci/operator-keypairs/id3.json b/ci/operator-keypairs/id3.json new file mode 100644 index 000000000..7bc96cdf7 --- /dev/null +++ b/ci/operator-keypairs/id3.json @@ -0,0 +1 @@ +[109,212,150,39,229,176,85,103,100,217,115,40,197,125,185,154,137,194,7,69,141,239,74,113,237,187,26,120,169,5,255,32,35,79,159,50,228,49,216,5,51,156,105,212,248,146,245,190,201,70,233,200,237,253,215,78,68,113,248,0,238,15,136,200] \ No newline at end of file diff --git a/ci/operator-keypairs/id30.json b/ci/operator-keypairs/id30.json new file mode 100644 index 000000000..b6b4a9b64 --- /dev/null +++ b/ci/operator-keypairs/id30.json @@ -0,0 +1 @@ +[164,184,180,94,178,30,255,212,198,57,93,119,61,202,55,97,47,134,31,115,120,201,199,32,163,108,202,131,121,135,128,6,68,231,110,177,230,42,56,26,247,172,232,205,110,76,106,16,153,106,234,56,11,247,17,237,241,11,56,21,145,232,254,153] \ No newline at end of file diff --git a/ci/operator-keypairs/id4.json b/ci/operator-keypairs/id4.json new file mode 100644 index 000000000..f0ca8465a --- /dev/null +++ b/ci/operator-keypairs/id4.json @@ -0,0 +1 @@ +[214,210,14,130,12,222,73,66,181,175,176,156,48,43,71,4,216,168,159,87,124,129,219,255,7,252,25,161,46,177,45,113,108,56,97,99,254,82,6,60,29,65,233,96,140,158,29,212,193,138,51,31,126,138,226,116,105,127,95,121,156,23,55,54] \ No newline at end of file diff --git a/ci/operator-keypairs/id5.json b/ci/operator-keypairs/id5.json new file mode 100644 index 000000000..1a5e67907 --- /dev/null +++ b/ci/operator-keypairs/id5.json @@ -0,0 +1 @@ +[135,36,157,212,37,148,54,141,6,153,179,125,174,108,39,176,152,84,179,2,79,124,113,176,10,83,101,69,165,219,43,89,7,50,172,103,235,215,62,192,98,108,223,67,30,114,241,40,45,61,189,107,162,9,86,31,11,124,184,245,39,12,101,13] \ No newline at end of file diff --git a/ci/operator-keypairs/id6.json b/ci/operator-keypairs/id6.json new file mode 100644 index 000000000..e367a5373 --- /dev/null +++ b/ci/operator-keypairs/id6.json @@ -0,0 +1 @@ +[245,253,29,158,248,242,248,20,31,201,243,72,241,50,136,180,229,63,143,99,92,29,230,5,181,107,230,147,253,103,22,247,1,192,248,239,196,211,107,121,216,2,30,108,163,95,239,105,245,93,161,171,252,153,27,40,132,14,136,73,52,195,251,158] \ No newline at end of file diff --git a/ci/operator-keypairs/id7.json b/ci/operator-keypairs/id7.json new file mode 100644 index 000000000..088191ba9 --- /dev/null +++ b/ci/operator-keypairs/id7.json @@ -0,0 +1 @@ +[103,137,235,222,211,158,252,227,63,199,231,174,155,23,246,93,31,194,94,222,156,0,159,205,93,110,243,4,179,21,145,13,236,212,22,139,247,96,23,122,167,37,106,151,101,126,240,202,104,15,176,243,57,143,103,132,64,211,83,237,3,68,189,154] \ No newline at end of file diff --git a/ci/operator-keypairs/id8.json b/ci/operator-keypairs/id8.json new file mode 100644 index 000000000..515cd77c5 --- /dev/null +++ b/ci/operator-keypairs/id8.json @@ -0,0 +1 @@ +[43,222,228,254,6,34,211,192,249,60,124,254,19,117,183,192,163,79,160,118,73,194,91,40,166,32,139,101,161,204,226,199,202,125,95,169,241,101,72,248,194,7,102,135,184,165,73,207,166,48,108,180,62,205,193,52,91,86,120,152,102,112,62,105] \ No newline at end of file diff --git a/ci/operator-keypairs/id9.json b/ci/operator-keypairs/id9.json new file mode 100644 index 000000000..02f112529 --- /dev/null +++ b/ci/operator-keypairs/id9.json @@ -0,0 +1 @@ +[76,252,69,52,51,62,9,220,165,183,52,47,149,238,49,198,68,226,22,251,245,70,58,59,205,49,212,77,31,191,81,94,131,180,89,79,237,117,30,106,190,97,233,53,235,163,61,130,254,35,88,148,254,230,240,235,118,222,84,106,92,2,89,155] \ No newline at end of file diff --git a/ci/solana-run-neon.sh b/ci/solana-run-neon.sh new file mode 100755 index 000000000..78033faf5 --- /dev/null +++ b/ci/solana-run-neon.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +set -em + +NEON_BIN=/opt + +EVM_LOADER_AUTHORITY_KEYPAIR=${NEON_BIN}/evm_loader-keypair.json +EVM_LOADER_PROGRAM_ID_KEYPAIR=${NEON_BIN}/evm_loader-keypair.json +EVM_LOADER=$(solana address -k ${EVM_LOADER_PROGRAM_ID_KEYPAIR}) +EVM_LOADER_PATH=${NEON_BIN}/evm_loader.so + +METAPLEX=metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s +METAPLEX_PATH=${NEON_BIN}/metaplex.so + + +VALIDATOR_ARGS=( + --reset + --warp-slot 1 + --log-messages-bytes-limit 50000 + --ticks-per-slot 16 + --upgradeable-program ${EVM_LOADER} ${EVM_LOADER_PATH} ${EVM_LOADER_AUTHORITY_KEYPAIR} + --bpf-program ${METAPLEX} ${METAPLEX_PATH} + --limit-ledger-size 400000000 +) + +LIST_OF_TEST_PROGRAMS=("test_invoke_program" "counter" "cross_program_invocation" "transfer_sol" "transfer_tokens") + +for program in "${LIST_OF_TEST_PROGRAMS[@]}"; do + keypair="${NEON_BIN}/deploy/${program}/${program}-keypair.json" + address=$(solana address -k $keypair) + VALIDATOR_ARGS+=(--bpf-program $address ${NEON_BIN}/deploy/$program/$program.so) +done + +if [[ -n $GEYSER_PLUGIN_CONFIG ]]; then + echo "Using geyser plugin with config: $GEYSER_PLUGIN_CONFIG" + VALIDATOR_ARGS+=(--geyser-plugin-config $GEYSER_PLUGIN_CONFIG) +fi + +export RUST_LOG=solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=debug,solana_bpf_loader=debug,solana_rbpf=debug +solana-test-validator "${VALIDATOR_ARGS[@]}" > /dev/null & +./wait-for-solana.sh ${SOLANA_WAIT_TIMEOUT:-60} +./deploy-multi-tokens.sh + +neon-cli --url http://localhost:8899 --evm_loader $EVM_LOADER \ + --commitment confirmed \ + --keypair ${EVM_LOADER_AUTHORITY_KEYPAIR} \ + --solana_key_for_config BMp6gEnveANdvSvspESJUrNczuHz1GF5UQKjVLCkAZih \ + --loglevel trace init-environment --send-trx --keys-dir /opt/keys + +tail +1f test-ledger/validator.log diff --git a/ci/wait-for-neon.sh b/ci/wait-for-neon.sh new file mode 100755 index 000000000..878cbdc0f --- /dev/null +++ b/ci/wait-for-neon.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -euo pipefail + +: ${EVM_LOADER:=$(solana address -k evm_loader-keypair.json)} +: ${SOLANA_URL:?is not set} + +./wait-for-solana.sh "$@" + +if [ $# -eq 0 ]; then + if neon-cli --url $SOLANA_URL --commitment confirmed --evm_loader $EVM_LOADER --loglevel off init-environment > /dev/null; then + echo "Executed neon-cli init-environment successfully" + exit 0 + fi +else + WAIT_TIME=$1 + echo "Waiting $WAIT_TIME seconds for NeonEVM to be available at $SOLANA_URL:$EVM_LOADER" + for i in $(seq 1 $WAIT_TIME); do + echo "Try neon-cli init-environment count=$i" + if neon-cli --url $SOLANA_URL --commitment confirmed --evm_loader $EVM_LOADER --loglevel off init-environment > /dev/null; then + echo "Executed neon-cli init-environment successfully after count=$i" + exit 0 + fi + sleep 1 + done +fi + +echo "unable to connect to NeonEVM at $SOLANA_URL:$EVM_LOADER" +exit 1 diff --git a/ci/wait-for-solana.sh b/ci/wait-for-solana.sh new file mode 100755 index 000000000..87efc2ff1 --- /dev/null +++ b/ci/wait-for-solana.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -euo pipefail + +function check_solana() { + local DATA='{"jsonrpc":"2.0","id":1,"method":"getHealth"}' + local RESULT='"ok"' + local CHECK_COMMAND="curl http://localhost:8899 -s -X POST -H 'Content-Type: application/json' -d '$DATA' | grep -cF '$RESULT'" + + local CHECK_COMMAND_RESULT=$(eval $CHECK_COMMAND) + if [[ "$CHECK_COMMAND_RESULT" == "1" ]]; then + exit 0 + fi + exit 1 +} + +if [ $# -eq 0 ]; then + if $(check_solana); then exit 0; fi +else + WAIT_TIME=$1 + echo "Waiting $WAIT_TIME seconds for solana cluster to be available at localhost" + for i in $(seq 1 $WAIT_TIME); do + echo "Try solana getHealth count=$i" + if $(check_solana); then + echo "Executed solana getHealth successfully after count=$i" + exit 0 + fi + sleep 1 + done +fi + +echo "unable to connect to solana cluster localhost" +exit 1 diff --git a/evm_loader/Cargo.lock b/evm_loader/Cargo.lock index 39bf4d77d..0a0544808 100644 --- a/evm_loader/Cargo.lock +++ b/evm_loader/Cargo.lock @@ -12,6 +12,256 @@ dependencies = [ "regex", ] +[[package]] +name = "abi_stable" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d6512d3eb05ffe5004c59c206de7f99c34951504056ce23fc953842f12c445" +dependencies = [ + "abi_stable_derive", + "abi_stable_shared", + "const_panic", + "core_extensions", + "crossbeam-channel", + "generational-arena", + "libloading", + "lock_api", + "parking_lot", + "paste", + "repr_offset", + "rustc_version", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "abi_stable_derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7178468b407a4ee10e881bc7a328a65e739f0863615cca4429d43916b05e898" +dependencies = [ + "abi_stable_shared", + "as_derive_utils", + "core_extensions", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", + "typed-arena", +] + +[[package]] +name = "abi_stable_shared" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b5df7688c123e63f4d4d649cba63f2967ba7f7861b1664fca3f77d3dad2b63" +dependencies = [ + "core_extensions", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae682f693a9cd7b058f2b0b5d9a6d7728a8555779bedbbc35dd88528611d020" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.11", + "base64 0.22.1", + "bitflags 2.6.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.8.5", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd 0.13.2", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.72", +] + +[[package]] +name = "actix-request-identifier" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901b615d14f5272a2451016a3718c75bed90f3e0b44a7757208809a384c34166" +dependencies = [ + "actix-web", + "futures", + "uuid", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02303ce8d4e8be5b855af6cf3c3a08f3eff26880faad82bab679c22d3650cb5" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio 0.8.11", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1988c02af8d2b718c05bc4aeb6a66395b7cdf32858c2c71131e5637a8c05a9ff" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash 0.8.11", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -34,7 +284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", - "cipher 0.3.0", + "cipher", "cpufeatures", "opaque-debug", ] @@ -47,7 +297,7 @@ checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" dependencies = [ "aead", "aes", - "cipher 0.3.0", + "cipher", "ctr", "polyval", "subtle", @@ -56,24 +306,43 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.15", "once_cell", "version_check", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -89,6 +358,18 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -109,27 +390,164 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] -name = "array-init" -version = "2.1.0" +name = "aquamarine" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "as_derive_utils" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "ff3c96645900a44cf11941c111bd08a6573b0e2f9f69bc9264b179d8fae753c4" +dependencies = [ + "core_extensions", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "ascii" @@ -150,7 +568,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.20", + "time", ] [[package]] @@ -159,8 +577,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", "syn 1.0.109", "synstructure", ] @@ -171,8 +589,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -182,11 +600,22 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + [[package]] name = "async-compression" -version = "0.3.15" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" dependencies = [ "brotli", "flate2", @@ -197,89 +626,73 @@ dependencies = [ ] [[package]] -name = "async-mutex" -version = "1.4.0" +name = "async-ffi" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +checksum = "f4de21c0feef7e5a556e51af767c953f0501f7f300ba785cc99c47bdc8081a50" dependencies = [ - "event-listener", + "abi_stable", ] [[package]] -name = "async-trait" -version = "0.1.68" +name = "async-lock" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "event-listener", ] [[package]] -name = "atty" -version = "0.2.14" +name = "async-mutex" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", + "event-listener", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "async-trait" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] [[package]] -name = "axum" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" -dependencies = [ - "async-trait", - "axum-core", - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "hyper", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", ] [[package]] -name = "axum-core" -version = "0.3.4" +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "mime", - "rustversion", - "tower-layer", - "tower-service", + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", ] [[package]] @@ -296,9 +709,15 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -306,6 +725,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +dependencies = [ + "serde", +] + [[package]] name = "bincode" version = "1.3.3" @@ -321,6 +749,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + [[package]] name = "bitmaps" version = "2.1.0" @@ -330,18 +767,30 @@ dependencies = [ "typenum", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" -version = "1.3.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -375,31 +824,89 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" dependencies = [ - "borsh-derive", + "borsh-derive 0.9.3", "hashbrown 0.11.2", ] +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive 0.10.3", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive 1.5.1", + "cfg_aliases 0.2.1", +] + [[package]] name = "borsh-derive" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", + "borsh-derive-internal 0.9.3", + "borsh-schema-derive-internal 0.9.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal 0.10.3", + "borsh-schema-derive-internal 0.10.3", "proc-macro-crate 0.1.5", - "proc-macro2 1.0.56", + "proc-macro2", "syn 1.0.109", ] +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.72", + "syn_derive", +] + [[package]] name = "borsh-derive-internal" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", "syn 1.0.109", ] @@ -409,16 +916,27 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] name = "brotli" -version = "3.3.4" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -427,9 +945,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.4" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -441,20 +959,93 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" -version = "1.4.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", ] +[[package]] +name = "build-info" +version = "0.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8164a6499aae6b0085e6da8fa528c4b0c37fcb1ed3cc00e175e9f98005807ffc" +dependencies = [ + "bincode", + "build-info-common", + "build-info-proc", +] + +[[package]] +name = "build-info-build" +version = "0.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da85beeeebafe621669220862ddd8e08ac741a56d171e2ab11cb8a16544d1e6" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bincode", + "build-info-common", + "cargo_metadata", + "chrono", + "git2", + "glob", + "pretty_assertions", + "rustc_version", + "serde_json", + "zstd 0.13.2", +] + +[[package]] +name = "build-info-common" +version = "0.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e049ed5c6d1f7cb5d2ee6838b9a284a1a20b9c17c9504eae08253f428af2eaa" +dependencies = [ + "chrono", + "derive_more", + "semver", + "serde", +] + +[[package]] +name = "build-info-proc" +version = "0.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6be54b4df52147ae16dd43edf11990c1e628b25eaa8ed77a9b708c390f7963" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bincode", + "build-info-common", + "chrono", + "num-bigint 0.4.6", + "num-traits", + "proc-macro-error", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.72", + "zstd 0.13.2", +] + [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bv" @@ -466,37 +1057,82 @@ dependencies = [ "serde", ] +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.4.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" + +[[package]] +name = "bytestring" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +dependencies = [ + "bytes", +] + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "camino" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +dependencies = [ + "serde", +] [[package]] name = "caps" @@ -508,6 +1144,29 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cast" version = "0.2.7" @@ -519,11 +1178,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -532,20 +1192,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets 0.52.6", ] [[package]] @@ -557,16 +1228,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "clap" version = "2.34.0" @@ -575,7 +1236,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim 0.8.0", "textwrap 0.11.0", "unicode-width", @@ -584,18 +1245,18 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "clap_lex", - "indexmap", + "indexmap 1.9.3", "once_cell", "strsim 0.10.0", "termcolor", - "textwrap 0.16.0", + "textwrap 0.16.1", ] [[package]] @@ -609,17 +1270,19 @@ dependencies = [ [[package]] name = "clickhouse" -version = "0.11.5" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33816ee1fea4f60d97abfeb773b9b566ae85f8bfa891758d00a1fb1e5a606591" +checksum = "eab6d70c534d5e54680aae99712800fca8d048cdfad3fbd154daf823d444f08b" dependencies = [ "bstr", "bytes", "clickhouse-derive", "clickhouse-rs-cityhash-sys", "futures", - "hyper", - "hyper-tls", + "futures-channel", + "http-body-util", + "hyper 1.4.1", + "hyper-util", "lz4", "sealed", "serde", @@ -631,14 +1294,14 @@ dependencies = [ [[package]] name = "clickhouse-derive" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18af5425854858c507eec70f7deb4d5d8cec4216fcb086283a78872387281ea5" +checksum = "d70f3e2893f7d3e017eeacdc9a708fbc29a10488e3ebca21f9df6a5d2b616dbb" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.72", ] [[package]] @@ -650,16 +1313,6 @@ dependencies = [ "cc", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "combine" version = "3.8.1" @@ -673,17 +1326,26 @@ dependencies = [ "unreachable", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" -version = "0.15.5" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.42.0", + "windows-sys 0.52.0", ] [[package]] @@ -714,60 +1376,98 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "const_format" -version = "0.2.30" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.29" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "const_panic" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "unicode-xid 0.2.4", + "core-foundation-sys", + "libc", ] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "core-foundation-sys" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] -name = "core-foundation" -version = "0.9.3" +name = "core_extensions" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "92c71dc07c9721607e7a16108336048ee978c3a8b129294534272e8bac96c0ee" dependencies = [ - "core-foundation-sys", - "libc", + "core_extensions_proc_macros", ] [[package]] -name = "core-foundation-sys" -version = "0.8.4" +name = "core_extensions_proc_macros" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "69f3b219d28b6e3b4ac87bc1fc522e0803ab22e055da177bff0068c4150c61a6" [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -787,46 +1487,37 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.8.0", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -860,17 +1551,17 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ - "cipher 0.3.0", + "cipher", ] [[package]] name = "ctrlc" -version = "3.2.5" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ - "nix 0.26.2", - "windows-sys 0.45.0", + "nix 0.28.0", + "windows-sys 0.52.0", ] [[package]] @@ -888,54 +1579,59 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.94" +name = "darling" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", + "darling_core", + "darling_macro", ] [[package]] -name = "cxx-build" -version = "1.0.94" +name = "darling_core" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2 1.0.56", - "quote 1.0.26", - "scratch", - "syn 2.0.15", + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.72", ] [[package]] -name = "cxxbridge-flags" -version = "1.0.94" +name = "darling_macro" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.72", +] [[package]] -name = "cxxbridge-macro" -version = "1.0.94" +name = "dashmap" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "rayon", ] [[package]] name = "data-encoding" -version = "2.3.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der" @@ -955,17 +1651,51 @@ dependencies = [ "asn1-rs", "displaydoc", "nom", - "num-bigint 0.4.3", + "num-bigint 0.4.6", "num-traits", "rusticata-macros", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "derivation-path" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.72", +] + [[package]] name = "dialoguer" version = "0.10.4" @@ -978,6 +1708,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.9.0" @@ -989,15 +1731,24 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", "subtle", ] +[[package]] +name = "dir-diff" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ad16bf5f84253b50d6557681c58c3ab67c47c77d39fed9aeb56e947290bd10" +dependencies = [ + "walkdir", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -1021,38 +1772,44 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] -name = "dlopen" -version = "0.1.8" +name = "dlopen2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937" +checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" dependencies = [ - "dlopen_derive", - "lazy_static", + "dlopen2_derive", "libc", + "once_cell", "winapi", ] [[package]] -name = "dlopen_derive" -version = "0.1.4" +name = "dlopen2_derive" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581" +checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ - "libc", - "quote 0.6.13", - "syn 0.15.44", + "proc-macro2", + "quote", + "syn 2.0.72", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "eager" version = "0.1.0" @@ -1091,14 +1848,23 @@ dependencies = [ "derivation-path", "ed25519-dalek", "hmac 0.12.1", - "sha2 0.10.6", + "sha2 0.10.8", ] [[package]] name = "either" -version = "1.8.1" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elsa" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "d98e71ae4df57d214182a2e5cb90230c0192c6ddfcaa05c36453d46a54713e10" +dependencies = [ + "stable_deref_trait", +] [[package]] name = "encode_unicode" @@ -1108,43 +1874,43 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] [[package]] name = "enum-iterator" -version = "0.8.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2953d1df47ac0eb70086ccabf0275aa8da8591a28bd358ee2b52bd9f9e3ff9e9" +checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "0.8.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8958699f9359f0b04e691a13850d48b7de329138023876d07cbd024c2c820598" +checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] name = "enum_dispatch" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f36e95862220b211a6e2aa5eca09b4fa391b13cd52ceb8035a24bf65a79de2" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" dependencies = [ "once_cell", - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] @@ -1161,37 +1927,79 @@ dependencies = [ ] [[package]] -name = "environmental" -version = "1.1.4" +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +dependencies = [ + "serde", +] [[package]] name = "errno" -version = "0.3.1" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "ethabi" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" dependencies = [ - "cc", - "libc", + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.8", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", ] [[package]] name = "ethnum" -version = "1.3.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0198b9d0078e0f30dedc7acbb21c974e838fc8fae3ee170128658a98cb2c1c04" +checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" dependencies = [ "serde", ] @@ -1204,58 +2012,62 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "evm-loader" -version = "1.1.0-dev" +version = "1.15.0-dev" dependencies = [ + "allocator-api2", "arrayref", + "async-trait", "bincode", - "borsh", + "borsh 0.10.3", "cfg-if", - "const_format", - "environmental", "ethnum", "evm-loader-macro", "hex", "linked_list_allocator", "log", + "maybe-async", + "memoffset 0.9.1", "mpl-token-metadata", "ripemd", "rlp", "serde", "serde_bytes", - "serde_json", "solana-program", + "solana-sdk", "spl-associated-token-account", "spl-token", "static_assertions", "thiserror", + "tokio", ] [[package]] name = "evm-loader-macro" version = "1.0.0" dependencies = [ - "bs58", - "itertools", - "quote 1.0.26", + "bs58 0.5.1", + "itertools 0.13.0", + "proc-macro2", + "quote", "serde", - "syn 1.0.109", - "toml", + "syn 2.0.72", + "toml 0.8.16", ] [[package]] -name = "fallible-iterator" +name = "extensions" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "258f70bd2b060d448403a66d420e81dcac3e5247a4928a887404a5e03715e2e0" +dependencies = [ + "fxhash", +] [[package]] name = "fastrand" -version = "1.9.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "feature-probe" @@ -1272,16 +2084,49 @@ dependencies = [ "log", ] +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1305,24 +2150,36 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1335,9 +2192,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1345,15 +2202,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1362,38 +2219,44 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1416,6 +2279,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generational-arena" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e94aff08e743b651baaea359664321055749b398adff8740a7399af7796e7" +dependencies = [ + "cfg-if", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1452,9 +2324,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -1463,6 +2335,31 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "git2" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +dependencies = [ + "bitflags 2.6.0", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "goblin" version = "0.5.4" @@ -1471,33 +2368,33 @@ checksum = "a7666983ed0dd8d21a6f6576ee00053ca0926fb281a5522577a4dbd0f1b54143" dependencies = [ "log", "plain", - "scroll", + "scroll 0.11.0", ] [[package]] name = "goblin" -version = "0.6.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6b4de4a8eb6c46a8c77e1d3be942cb9a8bf073c22374578e5ba4b08ed0ff68" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" dependencies = [ "log", "plain", - "scroll", + "scroll 0.12.0", ] [[package]] name = "h2" -version = "0.3.18" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1519,7 +2416,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash", + "ahash 0.7.8", ] [[package]] @@ -1528,58 +2425,101 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", ] [[package]] -name = "heck" -version = "0.3.3" +name = "hashbrown" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "unicode-segmentation", + "ahash 0.8.11", ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "libc", + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hidapi" -version = "1.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7" +checksum = "9e58251020fe88fe0dae5ebcc1be92b4995214af84725b375d08354d0311c23c" dependencies = [ "cc", + "cfg-if", "libc", "pkg-config", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1604,7 +2544,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1620,9 +2560,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1631,26 +2582,49 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "http", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -1660,37 +2634,59 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2", "tokio", "tower-service", "tracing", "want", ] +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ - "http", - "hyper", + "futures-util", + "http 0.2.12", + "hyper 0.14.30", + "log", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", ] @@ -1702,41 +2698,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1758,6 +2789,69 @@ dependencies = [ "version_check", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "index_list" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb725b6505e51229de32027e0cfcd9db29da4d89156f9747b0a5195643fa3e1" + [[package]] name = "indexmap" version = "1.9.3" @@ -1766,123 +2860,335 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", + "serde", ] [[package]] name = "indicatif" -version = "0.16.2" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", - "lazy_static", + "instant", "number_prefix", - "regex", + "portable-atomic", + "unicode-width", ] [[package]] -name = "inout" -version = "0.1.3" +name = "instant" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "generic-array", + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc-core" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" +dependencies = [ + "futures", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "jsonrpc-v2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759affe8550a30591c68f5e85d1784f24dc65217d2cca765949857f844fcecb0" +dependencies = [ + "actix-service", + "actix-web", + "async-trait", + "bytes", + "erased-serde", + "extensions", + "futures", + "serde", + "serde_json", +] + +[[package]] +name = "jsonrpsee" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affdc52f7596ccb2d7645231fc6163bb314630c989b64998f3699a28b4d5d4dc" +dependencies = [ + "jsonrpsee-core 0.20.3", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types 0.20.3", + "jsonrpsee-ws-client", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935" +dependencies = [ + "futures-util", + "http 0.2.12", + "jsonrpsee-core 0.20.3", + "pin-project", + "rustls-native-certs", + "soketto", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b" +dependencies = [ + "anyhow", + "async-lock", + "async-trait", + "beef", + "futures-timer", + "futures-util", + "hyper 0.14.30", + "jsonrpsee-types 0.20.3", + "parking_lot", + "rand 0.8.5", + "rustc-hash", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b257e1ec385e07b0255dde0b933f948b5c8b8c28d42afda9587c3a967b896d" +dependencies = [ + "anyhow", + "async-trait", + "beef", + "futures-util", + "hyper 0.14.30", + "jsonrpsee-types 0.22.5", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", ] [[package]] -name = "instant" -version = "0.1.12" +name = "jsonrpsee-http-client" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "1ccf93fc4a0bfe05d851d37d7c32b7f370fe94336b52a2f0efc5f1981895c2e5" dependencies = [ - "cfg-if", + "async-trait", + "hyper 0.14.30", + "hyper-rustls", + "jsonrpsee-core 0.22.5", + "jsonrpsee-types 0.22.5", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url", ] [[package]] -name = "io-lifetimes" -version = "1.0.10" +name = "jsonrpsee-proc-macros" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "29110019693a4fa2dbda04876499d098fa16d70eba06b1e6e2b3f1b251419515" dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", + "heck 0.4.1", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "ipnet" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" - -[[package]] -name = "itertools" -version = "0.10.5" +name = "jsonrpsee-server" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "82c39a00449c9ef3f50b84fc00fc4acba20ef8f559f07902244abf4c15c5ab9c" dependencies = [ - "either", + "futures-util", + "http 0.2.12", + "hyper 0.14.30", + "jsonrpsee-core 0.20.3", + "jsonrpsee-types 0.20.3", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", ] [[package]] -name = "itoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "jobserver" -version = "0.1.26" +name = "jsonrpsee-types" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "5be0be325642e850ed0bdff426674d2e66b2b7117c9be23a7caef68a2902b7d9" dependencies = [ - "libc", + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", + "tracing", ] [[package]] -name = "js-sys" -version = "0.3.61" +name = "jsonrpsee-types" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "150d6168405890a7a3231a3c74843f58b8959471f6df76078db2619ddee1d07d" dependencies = [ - "wasm-bindgen", + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", ] [[package]] -name = "jsonrpc-core" -version = "18.0.0" +name = "jsonrpsee-ws-client" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" +checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0" dependencies = [ - "futures", - "futures-executor", - "futures-util", - "log", - "serde", - "serde_derive", - "serde_json", + "http 0.2.12", + "jsonrpsee-client-transport", + "jsonrpsee-core 0.20.3", + "jsonrpsee-types 0.20.3", + "url", ] [[package]] name = "keccak" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.142" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libgit2-sys" +version = "0.17.0+1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] [[package]] name = "libloading" @@ -1894,6 +3200,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + [[package]] name = "libsecp256k1" version = "0.6.0" @@ -1943,19 +3259,28 @@ dependencies = [ ] [[package]] -name = "link-cplusplus" -version = "1.0.8" +name = "libz-sys" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" dependencies = [ "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] -name = "linked-hash-map" -version = "0.5.6" +name = "light-poseidon" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint 0.4.6", + "thiserror", +] [[package]] name = "linked_list_allocator" @@ -1965,15 +3290,32 @@ checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" [[package]] name = "linux-raw-sys" -version = "0.3.3" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1981,18 +3323,24 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" dependencies = [ - "cfg-if", + "hashbrown 0.12.3", ] [[package]] name = "lz4" -version = "1.24.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +checksum = "958b4caa893816eea05507c20cfe47574a43d9a697138a7872990bba8a0ece68" dependencies = [ "libc", "lz4-sys", @@ -2000,9 +3348,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.4" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" dependencies = [ "cc", "libc", @@ -2014,29 +3362,25 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] -name = "matchit" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" - -[[package]] -name = "md-5" -version = "0.10.5" +name = "maybe-async" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ - "digest 0.10.6", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -2049,18 +3393,18 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -2091,93 +3435,104 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] -name = "mpl-token-auth-rules" -version = "1.4.1" +name = "mio" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c376f2cc7dae80e2949cd6ca8a2420b3c61c1ecb7a275c6433d9a4d2d24f994d" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ - "borsh", - "bytemuck", - "mpl-token-metadata-context-derive", - "num-derive", - "num-traits", - "rmp-serde", - "serde", - "shank", - "solana-program", - "solana-zk-token-sdk", - "thiserror", + "hermit-abi 0.3.9", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", ] [[package]] -name = "mpl-token-metadata" -version = "1.12.0" +name = "mockall" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f661ff8c1d64c48cf207c0d259783d411a4249058c1b861fabd8bb6ce30ae4d8" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" dependencies = [ - "arrayref", - "borsh", - "mpl-token-auth-rules", - "mpl-token-metadata-context-derive", - "mpl-utils", - "num-derive", - "num-traits", - "shank", - "solana-program", - "spl-associated-token-account", - "spl-token", - "thiserror", + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", ] [[package]] -name = "mpl-token-metadata-context-derive" -version = "0.2.1" +name = "mockall_derive" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12989bc45715b0ee91944855130131479f9c772e198a910c3eb0ea327d5bffc3" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ - "quote 1.0.26", + "cfg-if", + "proc-macro2", + "quote", "syn 1.0.109", ] [[package]] -name = "mpl-utils" -version = "0.2.0" +name = "modular-bitfield" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822133b6cba8f9a43e5e0e189813be63dd795858f54155c729833be472ffdb51" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" dependencies = [ - "arrayref", - "borsh", + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "mpl-token-metadata" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf0f61b553e424a6234af1268456972ee66c2222e1da89079242251fa7479e5" +dependencies = [ + "borsh 0.10.3", + "num-derive 0.3.3", + "num-traits", "solana-program", - "spl-token", + "thiserror", ] [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -2191,26 +3546,33 @@ dependencies = [ [[package]] name = "neon-api" -version = "0.1.0" +version = "1.15.0-dev" dependencies = [ - "axum", + "actix-request-identifier", + "actix-web", + "build-info", + "build-info-build", "clap 2.34.0", "ethnum", "evm-loader", + "hex", "neon-lib", "serde", "serde_json", + "solana-client", "solana-sdk", "tokio", - "tower", "tracing", + "tracing-appender", "tracing-subscriber", ] [[package]] name = "neon-cli" -version = "1.1.0-dev" +version = "1.15.0-dev" dependencies = [ + "build-info", + "build-info-build", "clap 2.34.0", "ethnum", "evm-loader", @@ -2229,59 +3591,130 @@ dependencies = [ [[package]] name = "neon-lib" -version = "0.1.0" +version = "1.15.0-dev" dependencies = [ + "abi_stable", + "anyhow", + "arrayref", + "async-ffi", "async-trait", - "axum", + "base64 0.22.1", "bincode", - "bs58", + "bs58 0.5.1", + "build-info", + "build-info-build", + "clap 2.34.0", "clickhouse", + "elsa", + "enum_dispatch", "ethnum", "evm-loader", - "goblin 0.6.1", + "goblin 0.8.2", "hex", + "hex-literal", + "hyper 0.14.30", + "jsonrpsee", "lazy_static", "log", - "postgres", + "neon-lib-interface", "rand 0.8.5", - "scroll", + "scroll 0.12.0", "serde", - "solana-clap-utils", + "serde_json", + "serde_with 3.9.0", + "solana-account-decoder", + "solana-accounts-db", + "solana-bpf-loader-program", "solana-cli", "solana-cli-config", "solana-client", + "solana-loader-v4-program", + "solana-program-runtime", + "solana-runtime", "solana-sdk", "solana-transaction-status", "spl-associated-token-account", "spl-token", + "strum 0.26.3", + "strum_macros 0.26.4", + "thiserror", + "tokio", + "tracing", + "web3", +] + +[[package]] +name = "neon-lib-interface" +version = "0.1.0" +dependencies = [ + "abi_stable", + "async-ffi", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "neon-rpc" +version = "1.15.0-dev" +dependencies = [ + "actix-web", + "build-info", + "build-info-build", + "clap 2.34.0", + "jsonrpc-v2", + "neon-lib", + "neon-lib-interface", + "semver", + "serde", + "serde_json", "thiserror", "tokio", - "tokio-postgres", "tracing", + "tracing-appender", + "tracing-subscriber", +] + +[[package]] +name = "neon-rpc-client" +version = "0.1.0" +dependencies = [ + "async-trait", + "build-info", + "build-info-build", + "jsonrpsee-core 0.22.5", + "jsonrpsee-http-client", + "jsonrpsee-types 0.22.5", + "neon-lib", + "serde", + "serde_json", + "thiserror", + "tokio", ] [[package]] name = "nix" -version = "0.24.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", + "memoffset 0.7.1", + "pin-utils", ] [[package]] name = "nix" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", + "cfg_aliases 0.1.1", "libc", - "static_assertions", ] [[package]] @@ -2294,6 +3727,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2331,11 +3770,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -2350,32 +3788,48 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", "syn 1.0.109", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -2396,42 +3850,63 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] -name = "num_cpus" -version = "1.15.0" +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" dependencies = [ - "hermit-abi 0.2.6", - "libc", + "num_enum_derive 0.6.1", ] [[package]] name = "num_enum" -version = "0.5.11" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ - "num_enum_derive", + "num_enum_derive 0.7.2", ] [[package]] name = "num_enum_derive" -version = "0.5.11" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro-crate 1.3.1", - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] @@ -2440,6 +3915,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "object" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.6.1" @@ -2451,23 +3935,23 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.50" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -2482,9 +3966,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] @@ -2495,9 +3979,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.85" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -2507,9 +3991,32 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "ouroboros" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" +dependencies = [ + "aliasable", + "ouroboros_macro", +] + +[[package]] +name = "ouroboros_macro" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "overload" @@ -2517,11 +4024,37 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -2529,22 +4062,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.5.3", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.52.6", ] [[package]] name = "paste" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pbkdf2" @@ -2561,7 +4094,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2575,9 +4108,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "percentage" @@ -2588,49 +4121,31 @@ dependencies = [ "num", ] -[[package]] -name = "phf" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2651,9 +4166,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plain" @@ -2674,56 +4189,52 @@ dependencies = [ ] [[package]] -name = "postgres" -version = "0.19.5" +name = "portable-atomic" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bed5017bc2ff49649c0075d0d7a9d676933c1292480c1d137776fb205b5cd18" -dependencies = [ - "bytes", - "fallible-iterator", - "futures-util", - "log", - "tokio", - "tokio-postgres", -] +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] -name = "postgres-protocol" -version = "0.6.5" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b7fa9f396f51dffd61546fd8573ee20592287996568e6175ceb0f8699ad75d" -dependencies = [ - "base64 0.21.0", - "byteorder", - "bytes", - "fallible-iterator", - "hmac 0.12.1", - "md-5", - "memchr", - "rand 0.8.5", - "sha2 0.10.6", - "stringprep", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "postgres-types" -version = "0.2.5" +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "predicates" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f028f05971fe20f512bcc679e2c10227e57809a3af86a7606304435bc8896cd6" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ - "array-init", - "bytes", - "chrono", - "fallible-iterator", - "postgres-protocol", - "uuid", + "difflib", + "float-cmp", + "itertools 0.10.5", + "normalize-line-endings", + "predicates-core", + "regex", ] [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "predicates-core" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] [[package]] name = "pretty-hex" @@ -2731,13 +4242,36 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -2747,23 +4281,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] -name = "proc-macro2" -version = "0.4.30" +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "unicode-xid 0.1.0", + "proc-macro2", + "quote", + "version_check", ] [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -2777,76 +4335,79 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "quinn" -version = "0.8.5" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b435e71d9bfa0d8889927231970c51fb89c58fa63bffcab117c9c7a41e5ef8f" +checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" dependencies = [ "bytes", - "futures-channel", - "futures-util", - "fxhash", + "pin-project-lite", "quinn-proto", "quinn-udp", + "rustc-hash", "rustls", "thiserror", "tokio", "tracing", - "webpki", ] [[package]] name = "quinn-proto" -version = "0.8.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fce546b9688f767a57530652488420d419a8b1f44a478b451c3d1ab6d992a55" +checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" dependencies = [ "bytes", - "fxhash", "rand 0.8.5", - "ring", + "ring 0.16.20", + "rustc-hash", "rustls", "rustls-native-certs", - "rustls-pemfile 0.2.1", "slab", "thiserror", "tinyvec", "tracing", - "webpki", ] [[package]] name = "quinn-udp" -version = "0.1.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07946277141531aea269befd949ed16b2c85a780ba1043244eda0969e538e54" +checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ - "futures-util", + "bytes", "libc", - "quinn-proto", - "socket2 0.4.9", - "tokio", + "socket2", "tracing", + "windows-sys 0.48.0", ] [[package]] name = "quote" -version = "0.6.13" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "proc-macro2 0.4.30", + "proc-macro2", ] [[package]] -name = "quote" -version = "1.0.26" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" -dependencies = [ - "proc-macro2 1.0.56", -] +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" @@ -2935,7 +4496,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.15", ] [[package]] @@ -2958,9 +4519,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -2968,25 +4529,23 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "rcgen" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" +checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ "pem", - "ring", - "time 0.3.20", + "ring 0.16.20", + "time", "yasna", ] @@ -3001,42 +4560,43 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom 0.2.9", - "redox_syscall 0.2.16", + "getrandom 0.2.15", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.7.3" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -3045,45 +4605,82 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "repr_offset" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1070755bd29dffc19d0971cab794e607839ba2ef4b69a9e6fbc8733c1b72ea" +dependencies = [ + "tstr", +] + [[package]] name = "reqwest" -version = "0.11.16" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "async-compression", - "base64 0.21.0", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls", - "rustls-pemfile 1.0.2", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", + "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower-service", @@ -3091,7 +4688,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg", ] @@ -3104,19 +4701,34 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "ripemd" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -3130,44 +4742,37 @@ dependencies = [ ] [[package]] -name = "rmp" -version = "0.8.11" +name = "route-recognizer" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" -dependencies = [ - "byteorder", - "num-traits", - "paste", -] +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] -name = "rmp-serde" -version = "1.1.1" +name = "rpassword" +version = "7.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" dependencies = [ - "byteorder", - "rmp", - "serde", + "libc", + "rtoolbox", + "windows-sys 0.48.0", ] [[package]] -name = "rpassword" -version = "6.0.1" +name = "rtoolbox" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf099a1888612545b683d2661a1940089f6c2e5a8e38979b2159da876bfd956" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" dependencies = [ "libc", - "serde", - "serde_json", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -3201,142 +4806,183 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.13" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring", + "ring 0.17.8", + "rustls-webpki", "sct", - "webpki", ] [[package]] name = "rustls-native-certs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.2", + "rustls-pemfile", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "0.2.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.13.1", + "base64 0.21.7", ] [[package]] -name = "rustls-pemfile" -version = "1.0.2" +name = "rustls-webpki" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "base64 0.21.0", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +dependencies = [ + "scroll_derive 0.11.1", +] [[package]] -name = "schannel" -version = "0.1.21" +name = "scroll" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" dependencies = [ - "windows-sys 0.42.0", + "scroll_derive 0.12.0", ] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "scroll_derive" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] [[package]] -name = "scratch" -version = "1.0.5" +name = "scroll_derive" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] [[package]] -name = "scroll" -version = "0.11.0" +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "scroll_derive", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] -name = "scroll_derive" -version = "0.11.0" +name = "sealed" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e" +checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] -name = "sct" -version = "0.7.0" +name = "secp256k1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ - "ring", - "untrusted", + "secp256k1-sys", ] [[package]] -name = "sealed" -version = "0.4.0" +name = "secp256k1-sys" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5e421024b5e5edfbaa8e60ecf90bda9dbffc602dbb230e6028763f85f0c68c" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" dependencies = [ - "heck", - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "cc", ] [[package]] name = "security-framework" -version = "2.8.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -3345,9 +4991,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -3355,66 +5001,80 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "seqlock" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b5c67b6f14ecc5b86c66fa63d76b5092352678545a8a3cdae80aef5128371910" +dependencies = [ + "parking_lot", +] [[package]] name = "serde" -version = "1.0.160" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" dependencies = [ + "indexmap 2.2.6", "itoa", + "memchr", "ryu", "serde", ] [[package]] -name = "serde_path_to_error" -version = "0.1.11" +name = "serde_spanned" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -3431,27 +5091,93 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "serde", + "serde_with_macros 2.3.3", +] + +[[package]] +name = "serde_with" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros 3.9.0", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "serde_with_macros" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "serde_yaml" -version = "0.8.26" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.2.6", + "itoa", "ryu", "serde", - "yaml-rust", + "unsafe-libyaml", ] [[package]] name = "sha-1" -version = "0.10.1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -3469,13 +5195,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -3492,53 +5218,19 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "keccak", ] -[[package]] -name = "shank" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63e565b5e95ad88ab38f312e89444c749360641c509ef2de0093b49f55974a5" -dependencies = [ - "shank_macro", -] - -[[package]] -name = "shank_macro" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63927d22a1e8b74bda98cc6e151fcdf178b7abb0dc6c4f81e0bbf5ffe2fc4ec8" -dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "shank_macro_impl", - "syn 1.0.109", -] - -[[package]] -name = "shank_macro_impl" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce03403df682f80f4dc1efafa87a4d0cb89b03726d0565e6364bdca5b9a441" -dependencies = [ - "anyhow", - "proc-macro2 1.0.56", - "quote 1.0.26", - "serde", - "syn 1.0.109", -] - [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -3551,9 +5243,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -3566,9 +5258,9 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "sized-chunks" @@ -3582,74 +5274,141 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.4.9" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] -name = "socket2" -version = "0.5.2" +name = "soketto" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d283f86695ae989d1e18440a943880967156325ba025f05049946bff47bcc2b" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" dependencies = [ - "libc", - "windows-sys 0.48.0", + "base64 0.13.1", + "bytes", + "futures", + "http 0.2.12", + "httparse", + "log", + "rand 0.8.5", + "sha-1", ] [[package]] name = "solana-account-decoder" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e4e7166dc87b235d087997fd89724d82d0b83f75551fbb1d3d692a47ac237f" +checksum = "b4a1297281b114406a9165c6f55ff9f8706f6244545194c7aa837f9b25dee16e" dependencies = [ "Inflector", - "base64 0.13.1", + "base64 0.21.7", "bincode", - "bs58", + "bs58 0.4.0", "bv", "lazy_static", "serde", "serde_derive", "serde_json", - "solana-address-lookup-table-program", "solana-config-program", "solana-sdk", - "solana-vote-program", "spl-token", "spl-token-2022", + "spl-token-group-interface", + "spl-token-metadata-interface", + "thiserror", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "solana-accounts-db" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff031129c39c32d0176be1e3bb9352dc83c521187058d6c16febfee2aedddca0" +dependencies = [ + "arrayref", + "bincode", + "blake3", + "bv", + "bytemuck", + "byteorder", + "bzip2", + "crossbeam-channel", + "dashmap", + "flate2", + "fnv", + "im", + "index_list", + "itertools 0.10.5", + "lazy_static", + "log", + "lz4", + "memmap2", + "modular-bitfield", + "num-derive 0.4.2", + "num-traits", + "num_cpus", + "num_enum 0.7.2", + "ouroboros", + "percentage", + "qualifier_attr", + "rand 0.8.5", + "rayon", + "regex", + "rustc_version", + "seqlock", + "serde", + "serde_derive", + "smallvec", + "solana-bucket-map", + "solana-config-program", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-measure", + "solana-metrics", + "solana-nohash-hasher", + "solana-program-runtime", + "solana-rayon-threadlimit", + "solana-sdk", + "solana-stake-program", + "solana-system-program", + "solana-vote-program", + "static_assertions", + "strum 0.24.1", + "strum_macros 0.24.3", + "tar", + "tempfile", "thiserror", - "zstd", ] [[package]] name = "solana-address-lookup-table-program" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8669314c1bab81668baff87db9e81fcf72e15f61d9904e423f13911d8e3949cc" +checksum = "622d95db00595a3dd2abd2ee22c37cf3744ba3df8f531d5c79faa3c155e2814b" dependencies = [ "bincode", "bytemuck", "log", - "num-derive", + "num-derive 0.4.2", "num-traits", "rustc_version", "serde", @@ -3663,16 +5422,16 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9492c78a61eb825fee7e2e632037b89ff6e09023a5d37ee6639318316fa071d2" +checksum = "9934493a6034c8cf05913ba6812c057f6cd4792ad75c335e8d971adf63ce01c7" dependencies = [ "bincode", "byteorder", "libsecp256k1", "log", + "scopeguard", "solana-measure", - "solana-metrics", "solana-program-runtime", "solana-sdk", "solana-zk-token-sdk", @@ -3680,16 +5439,33 @@ dependencies = [ "thiserror", ] +[[package]] +name = "solana-bucket-map" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da941e0bde672cd33696961450efa2efb8b46322c4f814c1894c9c264122d1b" +dependencies = [ + "bv", + "bytemuck", + "log", + "memmap2", + "modular-bitfield", + "num_enum 0.7.2", + "rand 0.8.5", + "solana-measure", + "solana-sdk", + "tempfile", +] + [[package]] name = "solana-clap-utils" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d294b68c3a9f67aedde39c799fed85139d779a332280094054446f5b55a2edad" +checksum = "811569ed647c15e97afd142297b0ad14a87149c51e11df740014daa81f297f8b" dependencies = [ "chrono", "clap 2.34.0", "rpassword", - "solana-perf", "solana-remote-wallet", "solana-sdk", "thiserror", @@ -3700,18 +5476,19 @@ dependencies = [ [[package]] name = "solana-cli" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e21daca34f4f0462c54931193e6e613c589bb3c312ee79f35dc0994c8fe8bd9f" +checksum = "d5a9e83ff7d553663d3d68405b425f2701412f60c72820a325ce4d907a834974" dependencies = [ "bincode", - "bs58", + "bs58 0.4.0", "clap 2.34.0", "console", "const_format", "criterion-stats", "crossbeam-channel", "ctrlc", + "hex", "humantime", "log", "num-traits", @@ -3722,7 +5499,6 @@ dependencies = [ "serde_derive", "serde_json", "solana-account-decoder", - "solana-address-lookup-table-program", "solana-bpf-loader-program", "solana-clap-utils", "solana-cli-config", @@ -3730,10 +5506,16 @@ dependencies = [ "solana-client", "solana-config-program", "solana-faucet", + "solana-loader-v4-program", "solana-logger", "solana-program-runtime", + "solana-pubsub-client", "solana-remote-wallet", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-rpc-client-nonce-utils", "solana-sdk", + "solana-tpu-client", "solana-transaction-status", "solana-version", "solana-vote-program", @@ -3745,9 +5527,9 @@ dependencies = [ [[package]] name = "solana-cli-config" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "073a01cadf661c50373549add14d21436dcfed0fbc88022817481c6a18340fc8" +checksum = "3cb4459594cbdbc6f3cd199bbca486f0ef7f1ecf107dce9cffa3f9d69df31dc7" dependencies = [ "dirs-next", "lazy_static", @@ -3761,12 +5543,12 @@ dependencies = [ [[package]] name = "solana-cli-output" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953484f2c794afb2643907158f3433c209becd12e3943449d281ef6c1b68c69e" +checksum = "eb454e8df10d664ef0e1137c69af0f2d9fa0de713c1b608656318a76a2f5d3cc" dependencies = [ "Inflector", - "base64 0.13.1", + "base64 0.21.7", "chrono", "clap 2.34.0", "console", @@ -3779,7 +5561,7 @@ dependencies = [ "solana-account-decoder", "solana-clap-utils", "solana-cli-config", - "solana-client", + "solana-rpc-client-api", "solana-sdk", "solana-transaction-status", "solana-vote-program", @@ -3788,77 +5570,112 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3c55fbbc68dbf7b2df2ba01038d6d2a65522deec3db9c7b15e9c8c1d9dc526" +checksum = "792ed1869858f35b4359f696771517b4848cd3ff6e2d58155c4bcb292f30166b" dependencies = [ - "async-mutex", "async-trait", - "base64 0.13.1", "bincode", - "bs58", - "bytes", - "clap 2.34.0", - "crossbeam-channel", - "enum_dispatch", + "dashmap", "futures", "futures-util", - "indexmap", + "indexmap 2.2.6", "indicatif", - "itertools", - "jsonrpc-core", - "lazy_static", "log", "quinn", - "quinn-proto", - "rand 0.7.3", - "rand_chacha 0.2.2", "rayon", - "reqwest", - "rustls", - "semver", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder", - "solana-clap-utils", - "solana-faucet", + "solana-connection-cache", "solana-measure", "solana-metrics", - "solana-net-utils", + "solana-pubsub-client", + "solana-quic-client", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-rpc-client-nonce-utils", "solana-sdk", "solana-streamer", - "solana-transaction-status", - "solana-version", - "solana-vote-program", - "spl-token-2022", + "solana-thin-client", + "solana-tpu-client", + "solana-udp-client", + "thiserror", + "tokio", +] + +[[package]] +name = "solana-compute-budget-program" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dde13cdf503b7269435ceee5a94f8c42313e9257df00e6c2aa06466345dd3fc" +dependencies = [ + "solana-program-runtime", + "solana-sdk", +] + +[[package]] +name = "solana-config-program" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6db8509d749ff1396a0b538ade995908361563b4f2c875b09cf096ba4ba0fae" +dependencies = [ + "bincode", + "chrono", + "serde", + "serde_derive", + "solana-program-runtime", + "solana-sdk", +] + +[[package]] +name = "solana-connection-cache" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257511b6c2b54c28eae24c5a9b7bab74522439fcd721d09e63092768333ba7b7" +dependencies = [ + "async-trait", + "bincode", + "crossbeam-channel", + "futures-util", + "indexmap 2.2.6", + "log", + "rand 0.8.5", + "rayon", + "rcgen", + "solana-measure", + "solana-metrics", + "solana-sdk", "thiserror", "tokio", - "tokio-stream", - "tokio-tungstenite", - "tungstenite", - "url", ] [[package]] -name = "solana-config-program" -version = "1.14.20" +name = "solana-cost-model" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6faa5fbf80687e39850c3139561b38d35f9d35e248adaccc4b6379ee41846638" +checksum = "c391b492eb79c24756ee149b19f5504f7a2554612a8d2860fb9701df4e202a8b" dependencies = [ - "bincode", - "chrono", - "serde", - "serde_derive", + "lazy_static", + "log", + "rustc_version", + "solana-address-lookup-table-program", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-config-program", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-loader-v4-program", + "solana-metrics", "solana-program-runtime", "solana-sdk", + "solana-stake-program", + "solana-system-program", + "solana-vote-program", ] [[package]] name = "solana-faucet" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3084d18786564211573d5043550ea2750c049eaf3c75ae35ed8e9e845a0dda" +checksum = "fdd43150e893ba19b5cb222cb0539feb232da55b89a1591cda63daf85cf7bd5d" dependencies = [ "bincode", "byteorder", @@ -3880,33 +5697,24 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a100b7fa8198c20354eb7256c0d9789107d8a62280221f3efe15f7c9dc4cec" +checksum = "3f498a2b290abca1cf77feacef01b904be725fd46a7aea5ba121cce8c1269dcf" dependencies = [ - "ahash", - "blake3", - "block-buffer 0.9.0", - "bs58", + "block-buffer 0.10.4", + "bs58 0.4.0", "bv", - "byteorder", - "cc", "either", "generic-array", - "getrandom 0.1.16", - "hashbrown 0.12.3", "im", "lazy_static", "log", "memmap2", - "once_cell", - "rand_core 0.6.4", "rustc_version", "serde", "serde_bytes", "serde_derive", - "serde_json", - "sha2 0.10.6", + "sha2 0.10.8", "solana-frozen-abi-macro", "subtle", "thiserror", @@ -3914,21 +5722,34 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f527f44601b35dd67d11bc72f2f7512976a466f9304ef574b87dac83ced8a42" +checksum = "e4ab48d1be18021f5c13f94671e766699511044f81aab3376313f6a2392f8fab" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", "rustc_version", - "syn 1.0.109", + "syn 2.0.72", +] + +[[package]] +name = "solana-loader-v4-program" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d5e1fcf36f771e210db47d9e4db1e88821d334b313e6fb3c19cf6a987617ffb" +dependencies = [ + "log", + "solana-measure", + "solana-program-runtime", + "solana-sdk", + "solana_rbpf", ] [[package]] name = "solana-logger" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8632c8bc480bb5615b70a18b807ede73024aebc7761503ff86a70b7f4906ae47" +checksum = "ed08bcdd54232d2017071a6f5d664b34649ef0110801ac310a01418215f22ff7" dependencies = [ "env_logger", "lazy_static", @@ -3937,9 +5758,9 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3d09e890127454f43ffc37a6e23a375fe86c5159c4e5d4767d446ddb7e93a3" +checksum = "5db05e4bba8562a2419cb980301152fc7f60f643065c3aba4b3b5d6e3bd66e45" dependencies = [ "log", "solana-sdk", @@ -3947,9 +5768,9 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185128a041b4c1eaeec7f2034673ce89f99ee364fcb4405313edb1bc26ac95dd" +checksum = "f7a77735beed78eb221e123e0d46a991dc91db9e199d5c5fdbea22a55149d162" dependencies = [ "crossbeam-channel", "gethostname", @@ -3957,23 +5778,24 @@ dependencies = [ "log", "reqwest", "solana-sdk", + "thiserror", ] [[package]] name = "solana-net-utils" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16497e2cc753a0a35fb34c73846495fa21ba04a4e64cb92d818c4cb31c935fcc" +checksum = "4f76d98286bdb149ce5375c3c7d7301e5d1bf7bf2576789d3fd488cf93d32471" dependencies = [ "bincode", - "clap 3.2.23", + "clap 3.2.25", "crossbeam-channel", "log", - "nix 0.24.3", - "rand 0.7.3", + "nix 0.26.4", + "rand 0.8.5", "serde", "serde_derive", - "socket2 0.4.9", + "socket2", "solana-logger", "solana-sdk", "solana-version", @@ -3981,27 +5803,35 @@ dependencies = [ "url", ] +[[package]] +name = "solana-nohash-hasher" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" + [[package]] name = "solana-perf" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc65488c030174215a0176e44f889aed937c172ea1886c878f25ad7ce63f189" +checksum = "3b0a1b27503716c3f5362c61215d2ccf88a3ecf95fce51b2b59951c38a9ad94c" dependencies = [ - "ahash", + "ahash 0.8.11", "bincode", "bv", "caps", "curve25519-dalek", - "dlopen", - "dlopen_derive", + "dlopen2", "fnv", "lazy_static", "libc", "log", - "nix 0.24.3", - "rand 0.7.3", + "nix 0.26.4", + "rand 0.8.5", "rayon", + "rustc_version", "serde", + "solana-frozen-abi", + "solana-frozen-abi-macro", "solana-metrics", "solana-rayon-threadlimit", "solana-sdk", @@ -4010,44 +5840,50 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ad5f48743ce505f6139a07e20aecdc689def12da7230fed661c2073ab97df8" +checksum = "d97cec6d3d60ef58168c8b3e97fd88e8903fa059eff6635361427c61c946ec1e" dependencies = [ - "base64 0.13.1", + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "base64 0.21.7", "bincode", - "bitflags", + "bitflags 2.6.0", "blake3", - "borsh", - "borsh-derive", - "bs58", + "borsh 0.10.3", + "borsh 0.9.3", + "borsh 1.5.1", + "bs58 0.4.0", "bv", "bytemuck", "cc", "console_error_panic_hook", "console_log", "curve25519-dalek", - "getrandom 0.2.9", - "itertools", + "getrandom 0.2.15", + "itertools 0.10.5", "js-sys", "lazy_static", "libc", "libsecp256k1", + "light-poseidon", "log", - "memoffset 0.6.5", - "num-derive", + "memoffset 0.9.1", + "num-bigint 0.4.6", + "num-derive 0.4.2", "num-traits", "parking_lot", - "rand 0.7.3", - "rand_chacha 0.2.2", + "rand 0.8.5", "rustc_version", "rustversion", "serde", "serde_bytes", "serde_derive", "serde_json", - "sha2 0.10.6", - "sha3 0.10.7", + "sha2 0.10.8", + "sha3 0.10.8", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-sdk-macro", @@ -4059,21 +5895,21 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e837e539fee15e553bfc6e041bcfbc1113db6e437c21f0319536c4ea1621b884" +checksum = "d4b76599d73401663bc1fde39f9fa5e538bd74451ea4a8d4e3ac14541be0a5de" dependencies = [ - "base64 0.13.1", + "base64 0.21.7", "bincode", "eager", "enum-iterator", - "itertools", + "itertools 0.10.5", "libc", - "libloading", "log", - "num-derive", + "num-derive 0.4.2", "num-traits", - "rand 0.7.3", + "percentage", + "rand 0.8.5", "rustc_version", "serde", "solana-frozen-abi", @@ -4081,14 +5917,67 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-sdk", + "solana_rbpf", + "thiserror", +] + +[[package]] +name = "solana-pubsub-client" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09aacbdbeaa5722b2ab1f843673f66c317609741f494a6ef11e2e8928cc7449e" +dependencies = [ + "crossbeam-channel", + "futures-util", + "log", + "reqwest", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-rpc-client-api", + "solana-sdk", + "thiserror", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tungstenite", + "url", +] + +[[package]] +name = "solana-quic-client" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eeabd12e1039aa62cad34179fc1f139fca5c7bfeb7683057643bebff3506b0" +dependencies = [ + "async-mutex", + "async-trait", + "futures", + "itertools 0.10.5", + "lazy_static", + "log", + "quinn", + "quinn-proto", + "rcgen", + "rustls", + "solana-connection-cache", + "solana-measure", + "solana-metrics", + "solana-net-utils", + "solana-rpc-client-api", + "solana-sdk", + "solana-streamer", "thiserror", + "tokio", ] [[package]] name = "solana-rayon-threadlimit" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa7a1bc3d3e611952e10c9447094f5c2816b5bb8122c6710a6f28395595a671" +checksum = "8ad791cc224c84d69498eeeaeadfc5af2cf710ec2719e48b51f2e2b6e67d8163" dependencies = [ "lazy_static", "num_cpus", @@ -4096,15 +5985,15 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e4fb5902f71b3965b75a6418874f1d2bff525e4e49108e5f2ea174a5213b66" +checksum = "fca3c68439865c87d5a01c26dc0dcbcce2d45331adf8f0bf92a7c7d44853fefb" dependencies = [ "console", "dialoguer", "hidapi", "log", - "num-derive", + "num-derive 0.4.2", "num-traits", "parking_lot", "qstring", @@ -4114,47 +6003,189 @@ dependencies = [ "uriparse", ] +[[package]] +name = "solana-rpc-client" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c9bdb88ebcea8b13103019ed0d39b7d4391dc84a0d614dc8ba2e1ca43468e9" +dependencies = [ + "async-trait", + "base64 0.21.7", + "bincode", + "bs58 0.4.0", + "indicatif", + "log", + "reqwest", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-rpc-client-api", + "solana-sdk", + "solana-transaction-status", + "solana-version", + "solana-vote-program", + "tokio", +] + +[[package]] +name = "solana-rpc-client-api" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bb93738a111d44dfeec6ccdd6a49f0478550a25a60e38badb2bd713599de44" +dependencies = [ + "base64 0.21.7", + "bs58 0.4.0", + "jsonrpc-core", + "reqwest", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-sdk", + "solana-transaction-status", + "solana-version", + "spl-token-2022", + "thiserror", +] + +[[package]] +name = "solana-rpc-client-nonce-utils" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e066df081489cd10c91648f1e87d2b945fac2fcdfb34a715234a0b079a4c4375" +dependencies = [ + "clap 2.34.0", + "solana-clap-utils", + "solana-rpc-client", + "solana-sdk", + "thiserror", +] + +[[package]] +name = "solana-runtime" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8e16747198f45ab41f146d1ab1782d83016fb9ed1ce7612b9174018f907bd5" +dependencies = [ + "aquamarine", + "arrayref", + "base64 0.21.7", + "bincode", + "blake3", + "bv", + "bytemuck", + "byteorder", + "bzip2", + "crossbeam-channel", + "dashmap", + "dir-diff", + "flate2", + "fnv", + "im", + "index_list", + "itertools 0.10.5", + "lazy_static", + "log", + "lru", + "lz4", + "memmap2", + "mockall", + "modular-bitfield", + "num-derive 0.4.2", + "num-traits", + "num_cpus", + "num_enum 0.7.2", + "ouroboros", + "percentage", + "qualifier_attr", + "rand 0.8.5", + "rayon", + "regex", + "rustc_version", + "serde", + "serde_derive", + "serde_json", + "solana-accounts-db", + "solana-address-lookup-table-program", + "solana-bpf-loader-program", + "solana-bucket-map", + "solana-compute-budget-program", + "solana-config-program", + "solana-cost-model", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-loader-v4-program", + "solana-measure", + "solana-metrics", + "solana-perf", + "solana-program-runtime", + "solana-rayon-threadlimit", + "solana-sdk", + "solana-stake-program", + "solana-system-program", + "solana-version", + "solana-vote", + "solana-vote-program", + "solana-zk-token-proof-program", + "solana-zk-token-sdk", + "static_assertions", + "strum 0.24.1", + "strum_macros 0.24.3", + "symlink", + "tar", + "tempfile", + "thiserror", + "zstd 0.11.2+zstd.1.5.2", +] + [[package]] name = "solana-sdk" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c515a5a5a5cdc115044c33959eb4d091680f5e7ca8be9eb5218fb0c21bf3568" +checksum = "1c335bdf35728ea876506babffcfd85fa4dd66af6438f9472afc91b278946909" dependencies = [ "assert_matches", - "base64 0.13.1", + "base64 0.21.7", "bincode", - "bitflags", - "borsh", - "bs58", + "bitflags 2.6.0", + "borsh 1.5.1", + "bs58 0.4.0", "bytemuck", "byteorder", "chrono", "derivation-path", - "digest 0.10.6", + "digest 0.10.7", "ed25519-dalek", "ed25519-dalek-bip32", "generic-array", "hmac 0.12.1", - "itertools", + "itertools 0.10.5", "js-sys", "lazy_static", "libsecp256k1", "log", "memmap2", - "num-derive", + "num-derive 0.4.2", "num-traits", + "num_enum 0.7.2", "pbkdf2 0.11.0", "qstring", + "qualifier_attr", "rand 0.7.3", - "rand_chacha 0.2.2", + "rand 0.8.5", "rustc_version", "rustversion", "serde", "serde_bytes", "serde_derive", "serde_json", - "sha2 0.10.6", - "sha3 0.10.7", + "serde_with 2.3.3", + "sha2 0.10.8", + "sha3 0.10.8", + "siphasher", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-logger", @@ -4166,69 +6197,143 @@ dependencies = [ ] [[package]] -name = "solana-sdk-macro" -version = "1.14.20" +name = "solana-sdk-macro" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba67050b90454a8638913a7d5775703c0557157def04ddcc8b59c964cda8535" +dependencies = [ + "bs58 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.72", +] + +[[package]] +name = "solana-security-txt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" + +[[package]] +name = "solana-stake-program" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ef08f7b2485af8578bfd4e23689286e5360b50d3cc9f350dfe3ad9fdabc679" +dependencies = [ + "bincode", + "log", + "rustc_version", + "solana-config-program", + "solana-program-runtime", + "solana-sdk", + "solana-vote-program", +] + +[[package]] +name = "solana-streamer" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e8269eaa4aef1a9697fe8b9f659402272746c3fdd374fbffa96f68fe6745a6" +dependencies = [ + "async-channel", + "bytes", + "crossbeam-channel", + "futures-util", + "histogram", + "indexmap 2.2.6", + "itertools 0.10.5", + "libc", + "log", + "nix 0.26.4", + "pem", + "percentage", + "pkcs8", + "quinn", + "quinn-proto", + "rand 0.8.5", + "rcgen", + "rustls", + "smallvec", + "solana-metrics", + "solana-perf", + "solana-sdk", + "thiserror", + "tokio", + "x509-parser", +] + +[[package]] +name = "solana-system-program" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22c8b55398485962ea6127fe84c5768a5ae4a4ecdbd80a0917eb6945593481f9" +dependencies = [ + "bincode", + "log", + "serde", + "serde_derive", + "solana-program-runtime", + "solana-sdk", +] + +[[package]] +name = "solana-thin-client" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bbc3ab3070c090e1a18fd5a0a07d729d0db2bc8524414dc3e16504286d38049" +checksum = "e5c174c724bbe19a53862156e1a5d2cf6cdf1bd5d5d9853b4696b2d9d836123b" dependencies = [ - "bs58", - "proc-macro2 1.0.56", - "quote 1.0.26", - "rustversion", - "syn 1.0.109", + "bincode", + "log", + "rayon", + "solana-connection-cache", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-sdk", ] [[package]] -name = "solana-streamer" -version = "1.14.20" +name = "solana-tpu-client" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54279b5018a6e9a98e4b4e5dad87953e64359a6f9ce3715ffcdee0691885b88" +checksum = "4dcdf9d7109d25900d0532539b00f53e820fb7200f6dc18f90e9d2be20c3d466" dependencies = [ - "crossbeam-channel", + "async-trait", + "bincode", "futures-util", - "histogram", - "indexmap", - "itertools", - "libc", + "indexmap 2.2.6", + "indicatif", "log", - "nix 0.24.3", - "pem", - "percentage", - "pkcs8", - "quinn", - "rand 0.7.3", - "rcgen", - "rustls", + "rayon", + "solana-connection-cache", + "solana-measure", "solana-metrics", - "solana-perf", + "solana-pubsub-client", + "solana-rpc-client", + "solana-rpc-client-api", "solana-sdk", "thiserror", "tokio", - "x509-parser", ] [[package]] name = "solana-transaction-status" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6fbba26070920a1d25be8b3a2835df337ddcb5df1e65e4bbed48a019604377" +checksum = "7595aef5a9ddfdecd0966cc12ad0006713e4f8d87eafb4cb7b60fb18d98eff3a" dependencies = [ "Inflector", - "base64 0.13.1", + "base64 0.21.7", "bincode", - "borsh", - "bs58", + "borsh 0.10.3", + "bs58 0.4.0", "lazy_static", "log", "serde", "serde_derive", "serde_json", "solana-account-decoder", - "solana-address-lookup-table-program", - "solana-measure", - "solana-metrics", "solana-sdk", - "solana-vote-program", "spl-associated-token-account", "spl-memo", "spl-token", @@ -4236,11 +6341,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "solana-udp-client" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a59c49a3766232005385bd70f15b0fce756fa6e73fb6397ce9ed34113f090e2" +dependencies = [ + "async-trait", + "solana-connection-cache", + "solana-net-utils", + "solana-sdk", + "solana-streamer", + "thiserror", + "tokio", +] + [[package]] name = "solana-version" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122eccec6f9124b9234fde0ba6da263198f8e403f58e6535cd342f5fd3bd6c76" +checksum = "73ece37d745e1fb3455acd69b8ba6ecea3ebcefde29e0f40ee8e6467acc4dc04" dependencies = [ "log", "rustc_version", @@ -4252,15 +6372,34 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-vote" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ca2744cf5509c7680a90d6704d00fbab899aa586542c4257fb23d4712b380c" +dependencies = [ + "crossbeam-channel", + "itertools 0.10.5", + "log", + "rustc_version", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk", + "solana-vote-program", + "thiserror", +] + [[package]] name = "solana-vote-program" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edcb5a3ff3a8e78441badd1f21d2d6244e33497ee341b1bcafea768c6b501e6" +checksum = "6944bb3f8f34cc815017f6a50027331f5e7f48321e7998f1572cc898438b6a8a" dependencies = [ "bincode", "log", - "num-derive", + "num-derive 0.4.2", "num-traits", "rustc_version", "serde", @@ -4268,30 +6407,43 @@ dependencies = [ "solana-frozen-abi", "solana-frozen-abi-macro", "solana-metrics", + "solana-program", "solana-program-runtime", "solana-sdk", "thiserror", ] +[[package]] +name = "solana-zk-token-proof-program" +version = "1.18.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bd480f4b1b87dea1cdca14f3c6ba8e778e88756dc44ac090e797aab9c8759d" +dependencies = [ + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "solana-program-runtime", + "solana-sdk", + "solana-zk-token-sdk", +] + [[package]] name = "solana-zk-token-sdk" -version = "1.14.20" +version = "1.18.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d51d131cdefcb621a8034321ce487c4f788e813f81ce81e4f65eed8d4b4f2aa" +checksum = "616130045004bccc9dd016fe03ac458db38bd61456f1d16f126acb60f968dcae" dependencies = [ "aes-gcm-siv", - "arrayref", - "base64 0.13.1", + "base64 0.21.7", "bincode", "bytemuck", "byteorder", - "cipher 0.4.4", "curve25519-dalek", "getrandom 0.1.16", - "itertools", + "itertools 0.10.5", "lazy_static", "merlin", - "num-derive", + "num-derive 0.4.2", "num-traits", "rand 0.7.3", "serde", @@ -4306,9 +6458,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.2.31" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80a28c5dfe7e8af38daa39d6561c8e8b9ed7a2f900951ebe7362ad6348d36c73" +checksum = "3d457cc2ba742c120492a64b7fa60e22c575e891f6b55039f4d736568fb112a3" dependencies = [ "byteorder", "combine", @@ -4318,8 +6470,9 @@ dependencies = [ "log", "rand 0.8.5", "rustc-demangle", - "scroll", + "scroll 0.11.0", "thiserror", + "winapi", ] [[package]] @@ -4328,6 +6481,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.5.4" @@ -4340,13 +6499,13 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "1.1.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4" +checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", - "borsh", - "num-derive", + "borsh 0.10.3", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-token", @@ -4354,64 +6513,209 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-discriminator" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" +dependencies = [ + "quote", + "spl-discriminator-syn", + "syn 2.0.72", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.72", + "thiserror", +] + [[package]] name = "spl-memo" -version = "3.0.1" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a" +dependencies = [ + "solana-program", +] + +[[package]] +name = "spl-pod" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" +dependencies = [ + "borsh 0.10.3", + "bytemuck", + "solana-program", + "solana-zk-token-sdk", + "spl-program-error", +] + +[[package]] +name = "spl-program-error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" +dependencies = [ + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-program-error-derive", + "thiserror", +] + +[[package]] +name = "spl-program-error-derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1845dfe71fd68f70382232742e758557afe973ae19e6c06807b2c30f5d5cb474" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.72", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" +checksum = "615d381f48ddd2bb3c57c7f7fb207591a2a05054639b18a62e785117dd7a8683" dependencies = [ + "bytemuck", "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", ] [[package]] name = "spl-token" -version = "3.5.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e85e168a785e82564160dcb87b2a8e04cee9bfd1f4d488c729d53d6a4bd300d" +checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.3.3", "num-traits", - "num_enum", + "num_enum 0.6.1", "solana-program", "thiserror", ] [[package]] name = "spl-token-2022" -version = "0.6.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47" +checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.2", "num-traits", - "num_enum", + "num_enum 0.7.2", "solana-program", + "solana-security-txt", "solana-zk-token-sdk", "spl-memo", + "spl-pod", "spl-token", + "spl-token-group-interface", + "spl-token-metadata-interface", + "spl-transfer-hook-interface", + "spl-type-length-value", "thiserror", ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "spl-token-group-interface" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", +] [[package]] -name = "stringprep" -version = "0.1.2" +name = "spl-token-metadata-interface" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "borsh 0.10.3", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-tlv-account-resolution", + "spl-type-length-value", +] + +[[package]] +name = "spl-type-length-value" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.8.0" @@ -4424,6 +6728,53 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.72", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4431,15 +6782,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] -name = "syn" -version = "0.15.44" +name = "symlink" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid 0.1.0", -] +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" [[package]] name = "syn" @@ -4447,22 +6793,34 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.15" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -4475,34 +6833,77 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2", + "quote", "syn 1.0.109", - "unicode-xid 0.2.4", + "unicode-xid", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +dependencies = [ + "filetime", + "libc", + "xattr", ] [[package]] name = "tempfile" -version = "3.5.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "textwrap" version = "0.11.0" @@ -4514,28 +6915,28 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] @@ -4546,32 +6947,24 @@ checksum = "bcbb6aa301e5d3b0b5ef639c9a9c7e2f1c944f177b460c04dc24c69b1fa2bd99" [[package]] name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "time" -version = "0.1.45" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "cfg-if", + "once_cell", ] [[package]] name = "time" -version = "0.3.20" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ + "deranged", "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -4579,16 +6972,17 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -4611,11 +7005,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -4628,32 +7031,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.1", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.4.9", + "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] @@ -4666,46 +7068,21 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-postgres" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e89f6234aa8fd43779746012fcf53603cdb91fdd8399aa0de868c2d56b6dde1" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator", - "futures-channel", - "futures-util", - "log", - "parking_lot", - "percent-encoding", - "phf", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "socket2 0.5.2", - "tokio", - "tokio-util", -] - [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", - "webpki", ] [[package]] name = "tokio-stream" -version = "0.1.12" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -4714,9 +7091,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.17.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", @@ -4724,22 +7101,21 @@ dependencies = [ "tokio", "tokio-rustls", "tungstenite", - "webpki", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -4751,21 +7127,60 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.17", +] + [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow 0.5.40", +] [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.22.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" dependencies = [ - "indexmap", + "indexmap 2.2.6", + "serde", + "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.15", ] [[package]] @@ -4798,33 +7213,44 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -4832,20 +7258,20 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -4861,76 +7287,96 @@ dependencies = [ [[package]] name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tstr" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "7f8e0294f14baae476d0dd0a2d780b2e24d66e349a9de876f5126777a37bdba7" +dependencies = [ + "tstr_proc_macros", +] + +[[package]] +name = "tstr_proc_macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78122066b0cb818b8afd08f7ed22f7fdbc3e90815035726f0840d0d26c0747a" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "base64 0.13.1", "byteorder", "bytes", - "http", + "data-encoding", + "http 0.2.12", "httparse", "log", "rand 0.8.5", "rustls", - "sha-1", + "sha1", "thiserror", "url", "utf-8", - "webpki", - "webpki-roots", + "webpki-roots 0.24.0", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - [[package]] name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "unicode-xid" -version = "0.1.0" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -4957,12 +7403,24 @@ dependencies = [ "void", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "uriparse" version = "0.6.4" @@ -4975,12 +7433,12 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", ] @@ -4992,9 +7450,12 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "0.8.2" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom 0.2.15", +] [[package]] name = "valuable" @@ -5026,13 +7487,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -5042,12 +7512,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -5056,9 +7520,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5066,24 +7530,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.72", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -5093,62 +7557,106 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ - "quote 1.0.26", + "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki" -version = "0.22.0" +name = "web3" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5388522c899d1e1c96a4c307e3797e0f697ba7c77dd8e0e625ecba9dd0342937" +dependencies = [ + "arrayvec", + "base64 0.21.7", + "bytes", + "derive_more", + "ethabi", + "ethereum-types", + "futures", + "futures-timer", + "headers", + "hex", + "idna 0.4.0", + "jsonrpc-core", + "log", + "once_cell", + "parking_lot", + "pin-project", + "reqwest", + "rlp", + "secp256k1", + "serde", + "serde_json", + "soketto", + "tiny-keccak", + "tokio", + "tokio-stream", + "tokio-util", + "url", + "web3-async-native-tls", +] + +[[package]] +name = "web3-async-native-tls" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb" dependencies = [ - "ring", - "untrusted", + "native-tls", + "thiserror", + "tokio", + "url", ] [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" dependencies = [ - "webpki", + "rustls-webpki", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "winapi" version = "0.3.9" @@ -5167,11 +7675,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -5181,177 +7689,188 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.52.6", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.4.1" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "557404e450152cd6795bb558bca69e43c585055f4606e3bcae5894fc6dac9ba0" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", ] [[package]] @@ -5369,25 +7888,53 @@ dependencies = [ "oid-registry", "rusticata-macros", "thiserror", - "time 0.3.20", + "time", ] [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "xattr" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ - "linked-hash-map", + "libc", + "linux-raw-sys", + "rustix", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "yasna" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time 0.3.20", + "time", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] @@ -5405,9 +7952,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] @@ -5416,7 +7963,16 @@ version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ - "zstd-safe", + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe 7.2.0", ] [[package]] @@ -5429,13 +7985,21 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "zstd-safe" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" +dependencies = [ + "zstd-sys", +] + [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.12+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/evm_loader/Cargo.toml b/evm_loader/Cargo.toml index 5081e7429..d91765a5d 100644 --- a/evm_loader/Cargo.toml +++ b/evm_loader/Cargo.toml @@ -4,6 +4,27 @@ members = [ 'api', 'cli', 'lib', + 'lib-interface', + 'rpc', + 'rpc-client', 'program', - 'program-macro' + 'program-macro', ] + +[workspace.dependencies] +solana-clap-utils = "=1.18.18" +solana-cli = "=1.18.18" +solana-cli-config = "=1.18.18" +solana-client = "=1.18.18" +solana-account-decoder = "=1.18.18" +solana-program = { version = "=1.18.18", default-features = false } +solana-sdk = "=1.18.18" +solana-program-runtime = "=1.18.18" +solana-runtime = { version = "=1.18.18", features = ["dev-context-only-utils"] } +solana-accounts-db = "=1.18.18" +solana-bpf-loader-program = "=1.18.18" +solana-loader-v4-program = "=1.18.18" +solana-transaction-status = "=1.18.18" + +[profile.test] +debug = true diff --git a/evm_loader/api/Cargo.toml b/evm_loader/api/Cargo.toml index d849b642c..b4b0939fc 100644 --- a/evm_loader/api/Cargo.toml +++ b/evm_loader/api/Cargo.toml @@ -1,20 +1,27 @@ [package] name = "neon-api" -version = "0.1.0" +version = "1.15.0-dev" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "2.33.3" -evm-loader = { path = "../program", default_features = false, features = ["log", "tracing"] } -solana-sdk = "=1.14.20" -serde = "1.0.147" -serde_json = "1.0.85" -ethnum = { version = "1", default_features = false, features = [ "serde" ] } +clap = "2.34.0" +evm-loader = { path = "../program", default-features = false, features = ["log"] } +solana-sdk.workspace = true +solana-client.workspace = true +serde = "1.0.204" +serde_json = { version = "1.0.121", features = ["preserve_order"] } +ethnum = { version = "1.5", default-features = false, features = ["serde"] } tokio = { version = "1", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -axum = "0.6" -tower = { version = "0.4", features = ["make"] } +tracing-appender = "0.2.3" neon-lib = { path = "../lib" } +actix-web = "4.8.0" +actix-request-identifier = "4.2.0" +hex = "0.4.3" +build-info = "0.0.37" + +[build-dependencies] +build-info-build = "0.0.37" diff --git a/evm_loader/api/build.rs b/evm_loader/api/build.rs new file mode 100644 index 000000000..d36778f60 --- /dev/null +++ b/evm_loader/api/build.rs @@ -0,0 +1,3 @@ +fn main() { + build_info_build::build_script(); +} diff --git a/evm_loader/api/src/api_server/handlers/build_info.rs b/evm_loader/api/src/api_server/handlers/build_info.rs new file mode 100644 index 000000000..e57d05a4a --- /dev/null +++ b/evm_loader/api/src/api_server/handlers/build_info.rs @@ -0,0 +1,11 @@ +use crate::build_info::get_build_info; +use actix_web::get; +use actix_web::http::StatusCode; +use actix_web::web::Json; +use actix_web::Responder; + +#[tracing::instrument(ret)] +#[get("/build-info")] +pub async fn build_info_route() -> impl Responder { + (Json(get_build_info()), StatusCode::OK) +} diff --git a/evm_loader/api/src/api_server/handlers/emulate.rs b/evm_loader/api/src/api_server/handlers/emulate.rs index 9f0160fb9..c330b66b8 100644 --- a/evm_loader/api/src/api_server/handlers/emulate.rs +++ b/evm_loader/api/src/api_server/handlers/emulate.rs @@ -1,45 +1,42 @@ -use axum::{http::StatusCode, Json}; +#![allow(clippy::future_not_send)] + +use actix_request_identifier::RequestId; +use actix_web::{http::StatusCode, post, web::Json, Responder}; +use neon_lib::tracing::tracers::TracerTypeEnum; use std::convert::Into; +use tracing::info; -use crate::{ - commands::emulate as EmulateCommand, - context, - types::{request_models::EmulateRequestModel, trace::TraceCallConfig}, - NeonApiState, -}; +use crate::api_server::handlers::process_error; +use crate::{commands::emulate as EmulateCommand, types::EmulateApiRequest, NeonApiState}; -use super::{parse_emulation_params, process_error, process_result}; +use super::process_result; +#[tracing::instrument(skip_all, fields(id = request_id.as_str()))] +#[post("/emulate")] pub async fn emulate( - axum::extract::State(state): axum::extract::State, - Json(emulate_request): Json, -) -> (StatusCode, Json) { - let tx = emulate_request.tx_params.into(); + state: NeonApiState, + request_id: RequestId, + Json(emulate_request): Json, +) -> impl Responder { + info!("emulate_request={:?}", emulate_request); + + let slot = emulate_request.slot; + let index = emulate_request.tx_index_in_block; - let rpc_client = match context::build_rpc_client(&state.config, emulate_request.slot) { - Ok(rpc_client) => rpc_client, + let rpc = match state.build_rpc(slot, index).await { + Ok(rpc) => rpc, Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), }; - let context = context::create(rpc_client, state.config.clone()); - - let (token, chain, steps, accounts, solana_accounts) = - parse_emulation_params(&state.config, &context, &emulate_request.emulation_params).await; - process_result( &EmulateCommand::execute( - context.rpc_client.as_ref(), + &rpc, state.config.evm_loader, - tx, - token, - chain, - steps, - state.config.commitment, - &accounts, - &solana_accounts, - TraceCallConfig::default(), + emulate_request.body, + None::, ) .await + .map(|(response, _)| response) .map_err(Into::into), ) } diff --git a/evm_loader/api/src/api_server/handlers/emulate_hash.rs b/evm_loader/api/src/api_server/handlers/emulate_hash.rs deleted file mode 100644 index 78c3a514a..000000000 --- a/evm_loader/api/src/api_server/handlers/emulate_hash.rs +++ /dev/null @@ -1,58 +0,0 @@ -use axum::{http::StatusCode, Json}; -use std::convert::Into; - -use crate::{ - commands::emulate as EmulateCommand, - context, - types::{request_models::EmulateHashRequestModel, trace::TraceCallConfig}, - NeonApiState, -}; - -use super::{parse_emulation_params, process_error, process_result}; - -pub async fn emulate_hash( - axum::extract::State(state): axum::extract::State, - Json(emulate_hash_request): Json, -) -> (StatusCode, Json) { - let rpc_client = - match context::build_hash_rpc_client(&state.config, &emulate_hash_request.hash).await { - Ok(rpc_client) => rpc_client, - Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), - }; - - let tx = match rpc_client.get_transaction_data().await { - Ok(tx) => tx, - Err(e) => { - return process_error( - StatusCode::BAD_REQUEST, - &crate::errors::NeonError::SolanaClientError(e), - ) - } - }; - - let context = context::create(rpc_client, state.config.clone()); - - let (token, chain, steps, accounts, solana_accounts) = parse_emulation_params( - &state.config, - &context, - &emulate_hash_request.emulation_params, - ) - .await; - - process_result( - &EmulateCommand::execute( - context.rpc_client.as_ref(), - state.config.evm_loader, - tx, - token, - chain, - steps, - state.config.commitment, - &accounts, - &solana_accounts, - TraceCallConfig::default(), - ) - .await - .map_err(Into::into), - ) -} diff --git a/evm_loader/api/src/api_server/handlers/get_balance.rs b/evm_loader/api/src/api_server/handlers/get_balance.rs new file mode 100644 index 000000000..13b53a607 --- /dev/null +++ b/evm_loader/api/src/api_server/handlers/get_balance.rs @@ -0,0 +1,33 @@ +#![allow(clippy::future_not_send)] + +use crate::api_server::handlers::process_error; +use crate::commands::get_balance as GetBalanceCommand; +use crate::{types::GetBalanceRequest, NeonApiState}; +use actix_request_identifier::RequestId; +use actix_web::web::Json; +use actix_web::{http::StatusCode, post, Responder}; +use std::convert::Into; +use tracing::log::info; + +use super::process_result; + +#[tracing::instrument(skip_all, fields(id = request_id.as_str()))] +#[post("/balance")] +pub async fn get_balance( + state: NeonApiState, + request_id: RequestId, + Json(get_balance_request): Json, +) -> impl Responder { + info!("get_balance_request={:?}", get_balance_request); + + let rpc = match state.build_rpc(get_balance_request.slot, None).await { + Ok(rpc) => rpc, + Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), + }; + + process_result( + &GetBalanceCommand::execute(&rpc, &state.config.evm_loader, &get_balance_request.account) + .await + .map_err(Into::into), + ) +} diff --git a/evm_loader/api/src/api_server/handlers/get_config.rs b/evm_loader/api/src/api_server/handlers/get_config.rs new file mode 100644 index 000000000..4de5c1bf5 --- /dev/null +++ b/evm_loader/api/src/api_server/handlers/get_config.rs @@ -0,0 +1,29 @@ +#![allow(clippy::future_not_send)] + +use crate::api_server::handlers::process_error; +use crate::NeonApiState; +use actix_request_identifier::RequestId; +use actix_web::routes; +use actix_web::{http::StatusCode, Responder}; +use std::convert::Into; + +use crate::commands::get_config as GetConfigCommand; + +use super::process_result; + +#[tracing::instrument(skip_all, fields(id = request_id.as_str()))] +#[routes] +#[post("/config")] +#[get("/config")] +pub async fn get_config(state: NeonApiState, request_id: RequestId) -> impl Responder { + let rpc = match state.build_rpc(None, None).await { + Ok(rpc) => rpc, + Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), + }; + + process_result( + &GetConfigCommand::execute(&rpc, state.config.evm_loader) + .await + .map_err(Into::into), + ) +} diff --git a/evm_loader/api/src/api_server/handlers/get_contract.rs b/evm_loader/api/src/api_server/handlers/get_contract.rs new file mode 100644 index 000000000..6e5a47ba7 --- /dev/null +++ b/evm_loader/api/src/api_server/handlers/get_contract.rs @@ -0,0 +1,38 @@ +#![allow(clippy::future_not_send)] + +use crate::api_server::handlers::process_error; +use crate::commands::get_contract as GetContractCommand; +use crate::{types::GetContractRequest, NeonApiState}; +use actix_request_identifier::RequestId; +use actix_web::post; +use actix_web::web::Json; +use actix_web::{http::StatusCode, Responder}; +use std::convert::Into; +use tracing::info; + +use super::process_result; + +#[tracing::instrument(skip_all, fields(id = request_id.as_str()))] +#[post("/contract")] +pub async fn get_contract( + state: NeonApiState, + request_id: RequestId, + Json(get_contract_request): Json, +) -> impl Responder { + info!("get_contract_request={:?}", get_contract_request); + + let rpc = match state.build_rpc(get_contract_request.slot, None).await { + Ok(rpc) => rpc, + Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), + }; + + process_result( + &GetContractCommand::execute( + &rpc, + &state.config.evm_loader, + &get_contract_request.contract, + ) + .await + .map_err(Into::into), + ) +} diff --git a/evm_loader/api/src/api_server/handlers/get_ether_account_data.rs b/evm_loader/api/src/api_server/handlers/get_ether_account_data.rs deleted file mode 100644 index 8b9ed735a..000000000 --- a/evm_loader/api/src/api_server/handlers/get_ether_account_data.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::commands::get_ether_account_data as GetEtherAccountDataCommand; -use crate::{context, types::request_models::GetEtherRequest, NeonApiState}; -use axum::{ - extract::{Query, State}, - http::StatusCode, - Json, -}; -use std::convert::Into; - -use super::{process_error, process_result}; - -pub async fn get_ether_account_data( - Query(req_params): Query, - State(state): State, -) -> (StatusCode, Json) { - let rpc_client = match context::build_rpc_client(&state.config, req_params.slot) { - Ok(rpc_client) => rpc_client, - Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), - }; - - let context = context::create(rpc_client, state.config.clone()); - - process_result( - &GetEtherAccountDataCommand::execute( - context.rpc_client.as_ref(), - &state.config.evm_loader, - &req_params.ether, - ) - .await - .map_err(Into::into), - ) -} diff --git a/evm_loader/api/src/api_server/handlers/get_holder.rs b/evm_loader/api/src/api_server/handlers/get_holder.rs new file mode 100644 index 000000000..4ae6594c7 --- /dev/null +++ b/evm_loader/api/src/api_server/handlers/get_holder.rs @@ -0,0 +1,34 @@ +#![allow(clippy::future_not_send)] + +use crate::api_server::handlers::process_error; +use crate::commands::get_holder as GetHolderCommand; +use crate::{types::GetHolderRequest, NeonApiState}; +use actix_request_identifier::RequestId; +use actix_web::post; +use actix_web::web::Json; +use actix_web::{http::StatusCode, Responder}; +use std::convert::Into; +use tracing::info; + +use super::process_result; + +#[tracing::instrument(skip_all, fields(id = request_id.as_str()))] +#[post("/holder")] +pub async fn get_holder_account_data( + state: NeonApiState, + request_id: RequestId, + Json(get_holder_request): Json, +) -> impl Responder { + info!("get_holder_request={:?}", get_holder_request); + + let rpc = match state.build_rpc(get_holder_request.slot, None).await { + Ok(rpc) => rpc, + Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), + }; + + process_result( + &GetHolderCommand::execute(&rpc, &state.config.evm_loader, get_holder_request.pubkey) + .await + .map_err(Into::into), + ) +} diff --git a/evm_loader/api/src/api_server/handlers/get_storage_at.rs b/evm_loader/api/src/api_server/handlers/get_storage_at.rs index 8db99466b..ba4026155 100644 --- a/evm_loader/api/src/api_server/handlers/get_storage_at.rs +++ b/evm_loader/api/src/api_server/handlers/get_storage_at.rs @@ -1,32 +1,38 @@ -use crate::{context, types::request_models::GetStorageAtRequest, NeonApiState}; -use axum::{ - extract::{Query, State}, - http::StatusCode, - Json, -}; +#![allow(clippy::future_not_send)] + +use crate::api_server::handlers::process_error; +use crate::{types::GetStorageAtRequest, NeonApiState}; +use actix_request_identifier::RequestId; +use actix_web::post; +use actix_web::web::Json; +use actix_web::{http::StatusCode, Responder}; use std::convert::Into; +use tracing::info; use crate::commands::get_storage_at as GetStorageAtCommand; -use super::{process_error, process_result}; +use super::process_result; +#[tracing::instrument(skip_all, fields(id = request_id.as_str()))] +#[post("/storage")] pub async fn get_storage_at( - Query(req_params): Query, - State(state): State, -) -> (StatusCode, Json) { - let rpc_client = match context::build_rpc_client(&state.config, req_params.slot) { - Ok(rpc_client) => rpc_client, + state: NeonApiState, + request_id: RequestId, + Json(get_storage_at_request): Json, +) -> impl Responder { + info!("get_storage_at_request={:?}", get_storage_at_request); + + let rpc = match state.build_rpc(get_storage_at_request.slot, None).await { + Ok(rpc) => rpc, Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), }; - let context = context::create(rpc_client, state.config.clone()); - process_result( &GetStorageAtCommand::execute( - context.rpc_client.as_ref(), + &rpc, &state.config.evm_loader, - req_params.contract_id, - &req_params.index, + get_storage_at_request.contract, + get_storage_at_request.index, ) .await .map_err(Into::into), diff --git a/evm_loader/api/src/api_server/handlers/mod.rs b/evm_loader/api/src/api_server/handlers/mod.rs index 6ba074f6c..9645f6ae2 100644 --- a/evm_loader/api/src/api_server/handlers/mod.rs +++ b/evm_loader/api/src/api_server/handlers/mod.rs @@ -1,27 +1,23 @@ -use axum::http::StatusCode; -use axum::response::{IntoResponse, Response}; -use axum::Json; -use ethnum::U256; -use evm_loader::types::Address; +use actix_web::http::StatusCode; +use actix_web::web::Json; use serde::Serialize; use serde_json::{json, Value}; -use solana_sdk::pubkey::Pubkey; -use crate::commands::get_neon_elf::CachedElfParams; use crate::errors::NeonError; -use crate::{Config, Context, NeonApiResult}; +use crate::NeonApiResult; -use crate::types::request_models::EmulationParamsRequestModel; use std::net::AddrParseError; -use std::str::FromStr; +use tracing::error; +pub mod build_info; pub mod emulate; -pub mod emulate_hash; -pub mod get_ether_account_data; +pub mod get_balance; +pub mod get_config; +pub mod get_contract; +pub mod get_holder; pub mod get_storage_at; +pub mod simulate_solana; pub mod trace; -pub mod trace_hash; -pub mod trace_next_block; #[derive(Debug)] pub struct NeonApiError(pub NeonError); @@ -34,7 +30,7 @@ impl NeonApiError { impl From for NeonApiError { fn from(value: NeonError) -> Self { - NeonApiError(value) + Self(value) } } @@ -46,98 +42,33 @@ impl From for NeonError { impl From for NeonApiError { fn from(value: AddrParseError) -> Self { - NeonApiError(value.into()) + Self(value.into()) } } -impl IntoResponse for NeonApiError { - fn into_response(self) -> Response { - let (status, error_message) = (StatusCode::INTERNAL_SERVER_ERROR, self.0.to_string()); - - let body = Json(json!({ - "result": "error", - "error":error_message, - })); - - (status, body).into_response() - } -} - -pub fn u256_of(index: &str) -> Option { - if index.is_empty() { - return Some(U256::ZERO); - } - - U256::from_str_prefixed(index).ok() -} - -pub(crate) async fn parse_emulation_params( - config: &Config, - context: &Context, - params: &EmulationParamsRequestModel, -) -> (Pubkey, u64, u64, Vec
, Vec) { - // Read ELF params only if token_mint or chain_id is not set. - let mut token: Option = params.token_mint.map(Into::into); - let mut chain = params.chain_id; - if token.is_none() || chain.is_none() { - let cached_elf_params = CachedElfParams::new(config, context).await; - token = token.or_else(|| { - Some( - Pubkey::from_str( - cached_elf_params - .get("NEON_TOKEN_MINT") - .expect("NEON_TOKEN_MINT load error"), - ) - .expect("NEON_TOKEN_MINT Pubkey ctor error "), - ) - }); - chain = chain.or_else(|| { - Some( - u64::from_str( - cached_elf_params - .get("NEON_CHAIN_ID") - .expect("NEON_CHAIN_ID load error"), - ) - .expect("NEON_CHAIN_ID u64 ctor error"), - ) - }); - } - let token = token.expect("token_mint get error"); - let chain = chain.expect("chain_id get error"); - let max_steps = params.max_steps_to_execute; - - let accounts = params.cached_accounts.clone().unwrap_or_default(); - - let solana_accounts = params - .solana_accounts - .clone() - .map(|vec| vec.into_iter().map(Into::into).collect()) - .unwrap_or_default(); - - (token, chain, max_steps, accounts, solana_accounts) -} - fn process_result( result: &NeonApiResult, -) -> (StatusCode, Json) { +) -> (Json, StatusCode) { match result { Ok(value) => ( - StatusCode::OK, Json(json!({ "result": "success", "value": value, })), + StatusCode::OK, ), Err(e) => process_error(StatusCode::INTERNAL_SERVER_ERROR, &e.0), } } -fn process_error(status_code: StatusCode, e: &NeonError) -> (StatusCode, Json) { +fn process_error(status_code: StatusCode, e: &NeonError) -> (Json, StatusCode) { + error!("NeonError: {e}"); ( - status_code, Json(json!({ "result": "error", "error": e.to_string(), + "error_code": e.error_code(), })), + status_code, ) } diff --git a/evm_loader/api/src/api_server/handlers/simulate_solana.rs b/evm_loader/api/src/api_server/handlers/simulate_solana.rs new file mode 100644 index 000000000..986b827cb --- /dev/null +++ b/evm_loader/api/src/api_server/handlers/simulate_solana.rs @@ -0,0 +1,34 @@ +#![allow(clippy::future_not_send)] + +use actix_request_identifier::RequestId; +use actix_web::{http::StatusCode, post, web::Json, Responder}; +use std::convert::Into; +use tracing::info; + +use crate::api_server::handlers::process_error; +use crate::{ + commands::simulate_solana as SimulateSolanaCommand, types::SimulateSolanaRequest, NeonApiState, +}; + +use super::process_result; + +#[tracing::instrument(skip_all, fields(id = request_id.as_str()))] +#[post("/simulate_solana")] +pub async fn simulate_solana( + state: NeonApiState, + request_id: RequestId, + Json(emulate_request): Json, +) -> impl Responder { + info!("simulate_solana_request={:?}", emulate_request); + + let rpc = match state.build_rpc(None, None).await { + Ok(rpc) => rpc, + Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), + }; + + process_result( + &SimulateSolanaCommand::execute(&rpc, emulate_request) + .await + .map_err(Into::into), + ) +} diff --git a/evm_loader/api/src/api_server/handlers/trace.rs b/evm_loader/api/src/api_server/handlers/trace.rs index ad10577af..4723df2f4 100644 --- a/evm_loader/api/src/api_server/handlers/trace.rs +++ b/evm_loader/api/src/api_server/handlers/trace.rs @@ -1,46 +1,36 @@ -use axum::{http::StatusCode, Json}; +#![allow(clippy::future_not_send)] + +use actix_request_identifier::RequestId; +use actix_web::{http::StatusCode, post, web::Json, Responder}; use std::convert::Into; +use tracing::info; +use crate::api_server::handlers::process_error; use crate::commands::trace::trace_transaction; -use crate::{context, types::request_models::TraceRequestModel, NeonApiState}; +use crate::{types::EmulateApiRequest, NeonApiState}; -use super::{parse_emulation_params, process_error, process_result}; +use super::process_result; +#[tracing::instrument(skip_all, fields(id = request_id.as_str()))] +#[post("/trace")] pub async fn trace( - axum::extract::State(state): axum::extract::State, - Json(trace_request): Json, -) -> (StatusCode, Json) { - let tx = trace_request.emulate_request.tx_params.into(); - - let rpc_client = - match context::build_rpc_client(&state.config, trace_request.emulate_request.slot) { - Ok(rpc_client) => rpc_client, - Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), - }; + state: NeonApiState, + request_id: RequestId, + Json(trace_request): Json, +) -> impl Responder { + info!("trace_request={:?}", trace_request); - let context = context::create(rpc_client, state.config.clone()); + let slot = trace_request.slot; + let index = trace_request.tx_index_in_block; - let (token, chain, steps, accounts, solana_accounts) = parse_emulation_params( - &state.config, - &context, - &trace_request.emulate_request.emulation_params, - ) - .await; + let rpc = match state.build_rpc(slot, index).await { + Ok(rpc) => rpc, + Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), + }; process_result( - &trace_transaction( - context.rpc_client.as_ref(), - state.config.evm_loader, - tx, - token, - chain, - steps, - state.config.commitment, - &accounts, - &solana_accounts, - trace_request.trace_call_config.unwrap_or_default(), - ) - .await - .map_err(Into::into), + &trace_transaction(&rpc, state.config.evm_loader, trace_request.body) + .await + .map_err(Into::into), ) } diff --git a/evm_loader/api/src/api_server/handlers/trace_hash.rs b/evm_loader/api/src/api_server/handlers/trace_hash.rs deleted file mode 100644 index b3fac176c..000000000 --- a/evm_loader/api/src/api_server/handlers/trace_hash.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::convert::Into; - -use crate::{context, types::request_models::TraceHashRequestModel, NeonApiState}; -use axum::{http::StatusCode, Json}; -use neon_lib::commands::trace::trace_transaction; - -use super::{parse_emulation_params, process_error, process_result}; - -pub async fn trace_hash( - axum::extract::State(state): axum::extract::State, - Json(trace_hash_request): Json, -) -> (StatusCode, Json) { - let rpc_client = match context::build_hash_rpc_client( - &state.config, - &trace_hash_request.emulate_hash_request.hash, - ) - .await - { - Ok(rpc_client) => rpc_client, - Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), - }; - - let tx = match rpc_client.get_transaction_data().await { - Ok(tx) => tx, - Err(e) => { - return process_error( - StatusCode::BAD_REQUEST, - &crate::errors::NeonError::SolanaClientError(e), - ) - } - }; - - let context = context::create(rpc_client, state.config.clone()); - - let (token, chain, steps, accounts, solana_accounts) = parse_emulation_params( - &state.config, - &context, - &trace_hash_request.emulate_hash_request.emulation_params, - ) - .await; - - process_result( - &trace_transaction( - context.rpc_client.as_ref(), - state.config.evm_loader, - tx, - token, - chain, - steps, - state.config.commitment, - &accounts, - &solana_accounts, - trace_hash_request.trace_config.unwrap_or_default().into(), - ) - .await - .map_err(Into::into), - ) -} diff --git a/evm_loader/api/src/api_server/handlers/trace_next_block.rs b/evm_loader/api/src/api_server/handlers/trace_next_block.rs deleted file mode 100644 index d5fc2e8c4..000000000 --- a/evm_loader/api/src/api_server/handlers/trace_next_block.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::{ - api_server::handlers::process_error, - commands::trace::trace_block, - context, errors, - types::{request_models::TraceNextBlockRequestModel, IndexerDb}, - NeonApiState, -}; -use axum::http::StatusCode; -use axum::Json; -use std::sync::Arc; - -use super::{parse_emulation_params, process_result}; - -pub async fn trace_next_block( - axum::extract::State(state): axum::extract::State, - Json(trace_next_block_request): Json, -) -> (StatusCode, Json) { - let rpc_client = - match context::build_call_db_client(&state.config, trace_next_block_request.slot) { - Ok(rpc_client) => rpc_client, - Err(e) => return process_error(StatusCode::BAD_REQUEST, &e), - }; - - let context = context::create(rpc_client, Arc::clone(&state.config)); - - let (token, chain, steps, accounts, solana_accounts) = parse_emulation_params( - &state.config, - &context, - &trace_next_block_request.emulation_params, - ) - .await; - - let indexer_db = IndexerDb::new( - state - .config - .db_config - .as_ref() - .expect("db-config is required"), - ) - .await; - - // TODO: Query next block (which parent = slot) instead of getting slot + 1: - let transactions = match indexer_db - .get_block_transactions(trace_next_block_request.slot + 1) - .await - { - Ok(transactions) => transactions, - Err(e) => { - return process_error( - StatusCode::INTERNAL_SERVER_ERROR, - &errors::NeonError::PostgreError(e), - ) - } - }; - - process_result( - &trace_block( - context.rpc_client.as_ref(), - state.config.evm_loader, - transactions, - token, - chain, - steps, - state.config.commitment, - &accounts, - &solana_accounts, - &trace_next_block_request.trace_config.unwrap_or_default(), - ) - .await - .map_err(Into::into), - ) -} diff --git a/evm_loader/api/src/api_server/mod.rs b/evm_loader/api/src/api_server/mod.rs index 9b94b34ed..c3d449565 100644 --- a/evm_loader/api/src/api_server/mod.rs +++ b/evm_loader/api/src/api_server/mod.rs @@ -1,3 +1 @@ pub mod handlers; -pub mod routes; -pub mod state; diff --git a/evm_loader/api/src/api_server/routes.rs b/evm_loader/api/src/api_server/routes.rs deleted file mode 100644 index 154a701fb..000000000 --- a/evm_loader/api/src/api_server/routes.rs +++ /dev/null @@ -1,31 +0,0 @@ -use axum::{ - routing::{get, post}, - Router, -}; -use tower::ServiceBuilder; - -// use evm_loader::types::Address; -use crate::{ - api_server::handlers::{ - emulate::emulate, emulate_hash::emulate_hash, - get_ether_account_data::get_ether_account_data, get_storage_at::get_storage_at, - trace::trace, trace_hash::trace_hash, trace_next_block::trace_next_block, - }, - NeonApiState, -}; - -pub fn register(s: NeonApiState) -> Router { - ServiceBuilder::new().service::>( - Router::new() - .route("/emulate", post(emulate)) - .route("/emulate-hash", post(emulate_hash)) - .route("/emulate_hash", post(emulate_hash)) // Obsolete - .route("/get-storage-at", get(get_storage_at)) - .route("/get-ether-account-data", get(get_ether_account_data)) - .route("/trace", post(trace)) - .route("/trace-hash", post(trace_hash)) - .route("/trace_hash", post(trace_hash)) // Obsolete - .route("/trace-next-block", post(trace_next_block)) - .with_state(s), - ) -} diff --git a/evm_loader/api/src/api_server/state.rs b/evm_loader/api/src/api_server/state.rs deleted file mode 100644 index 039cc6c32..000000000 --- a/evm_loader/api/src/api_server/state.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::Config; -use std::sync::Arc; - -#[derive(Clone)] -pub struct State { - pub config: Arc, -} - -impl State { - pub fn new(config: Config) -> Self { - Self { - config: Arc::new(config), - } - } -} diff --git a/evm_loader/api/src/build_info.rs b/evm_loader/api/src/build_info.rs new file mode 100644 index 000000000..85f42da0d --- /dev/null +++ b/evm_loader/api/src/build_info.rs @@ -0,0 +1,7 @@ +use neon_lib::build_info_common::SlimBuildInfo; + +build_info::build_info!(fn build_info); + +pub fn get_build_info() -> SlimBuildInfo { + build_info().into() +} diff --git a/evm_loader/api/src/main.rs b/evm_loader/api/src/main.rs index 99ac6189c..b1ca52cc0 100644 --- a/evm_loader/api/src/main.rs +++ b/evm_loader/api/src/main.rs @@ -1,47 +1,60 @@ -#![allow(dead_code)] #![deny(warnings)] -#![deny(clippy::all, clippy::pedantic)] +#![deny(clippy::all, clippy::pedantic, clippy::nursery)] mod api_options; mod api_server; +#[allow(clippy::module_name_repetitions)] +mod build_info; +use actix_web::web; +use actix_web::App; +use actix_web::HttpServer; use api_server::handlers::NeonApiError; -use axum::Router; -pub use neon_lib::account_storage; +use neon_lib::abi::state::State; pub use neon_lib::commands; pub use neon_lib::config; -pub use neon_lib::context; pub use neon_lib::errors; -pub use neon_lib::event_listener; -pub use neon_lib::rpc; -pub use neon_lib::syscall_stubs; pub use neon_lib::types; +use tracing_appender::non_blocking::NonBlockingBuilder; -use std::sync::Arc; +use actix_request_identifier::RequestIdentifier; +use actix_web::web::Data; use std::{env, net::SocketAddr, str::FromStr}; +use crate::api_server::handlers::build_info::build_info_route; +use crate::api_server::handlers::emulate::emulate; +use crate::api_server::handlers::get_balance::get_balance; +use crate::api_server::handlers::get_config::get_config; +use crate::api_server::handlers::get_contract::get_contract; +use crate::api_server::handlers::get_holder::get_holder_account_data; +use crate::api_server::handlers::get_storage_at::get_storage_at; +use crate::api_server::handlers::simulate_solana::simulate_solana; +use crate::api_server::handlers::trace::trace; +use crate::build_info::get_build_info; pub use config::Config; -pub use context::Context; -use tokio::signal::{self}; +use tracing::info; +use tracing_subscriber::EnvFilter; type NeonApiResult = Result; -type NeonApiState = Arc; +type NeonApiState = Data; -#[tokio::main(flavor = "multi_thread", worker_threads = 512)] +#[actix_web::main] async fn main() -> NeonApiResult<()> { let options = api_options::parse(); // initialize tracing - tracing_subscriber::fmt::init(); + let (non_blocking, _guard) = NonBlockingBuilder::default() + .lossy(false) + .finish(std::io::stdout()); - let api_config = config::load_api_config_from_enviroment(); + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .with_writer(non_blocking) + .init(); - let config = config::create_from_api_comnfig(&api_config)?; + info!("{}", get_build_info()); - let state: NeonApiState = Arc::new(api_server::state::State::new(config)); - - let app = Router::new() - .nest("/api", api_server::routes::register(state.clone())) - .with_state(state.clone()); + let api_config = config::load_api_config_from_environment(); + let state: NeonApiState = Data::new(State::new(api_config).await); let listener_addr = options .value_of("host") @@ -52,38 +65,28 @@ async fn main() -> NeonApiResult<()> { ); let addr = SocketAddr::from_str(listener_addr.as_str())?; - tracing::debug!("listening on {}", addr); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .with_graceful_shutdown(shutdown_signal()) - .await - .unwrap(); + tracing::info!("listening on {}", addr); + HttpServer::new(move || { + App::new().service( + web::scope("/api") + .app_data(state.clone()) + .service(build_info_route) + .service(emulate) + .service(get_balance) + .service(get_contract) + .service(get_storage_at) + .service(get_config) + .service(get_holder_account_data) + .service(trace) + .service(simulate_solana) + .wrap(RequestIdentifier::with_uuid()), + ) + }) + .bind(addr) + .unwrap() + .run() + .await + .unwrap(); Ok(()) } - -async fn shutdown_signal() { - let ctrl_c = async { - signal::ctrl_c() - .await - .expect("failed to install Ctrl+C handler"); - }; - - #[cfg(unix)] - let terminate = async { - signal::unix::signal(signal::unix::SignalKind::terminate()) - .expect("failed to install signal handler") - .recv() - .await; - }; - - #[cfg(not(unix))] - let terminate = std::future::pending::<()>(); - - tokio::select! { - _ = ctrl_c => {}, - _ = terminate => {}, - } - - println!("signal received, starting graceful shutdown"); -} diff --git a/evm_loader/cli/Cargo.toml b/evm_loader/cli/Cargo.toml index 7cf0b1643..a2ff8a695 100644 --- a/evm_loader/cli/Cargo.toml +++ b/evm_loader/cli/Cargo.toml @@ -1,22 +1,26 @@ [package] name = "neon-cli" -version = "1.1.0-dev" +version = "1.15.0-dev" authors = ["NeonLabs Maintainers "] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "2.33.3" -evm-loader = { path = "../program", default_features = false, features = ["log", "tracing"] } -solana-sdk = "=1.14.20" -solana-client = "=1.14.20" -solana-clap-utils = "=1.14.20" -solana-cli-config = "=1.14.20" -hex = "0.4.2" -serde = "1.0.147" -serde_json = "1.0.85" -log = "0.4.17" +clap = "2.34.0" +evm-loader = { path = "../program", default-features = false, features = ["log"] } +solana-sdk.workspace = true +solana-client.workspace = true +solana-clap-utils.workspace = true +solana-cli-config.workspace = true +hex = "0.4.3" +serde = "1.0.204" +serde_json = { version = "1.0.121", features = ["preserve_order"] } +log = "0.4.22" fern = "0.6" -ethnum = { version = "1", default_features = false, features = [ "serde" ] } +ethnum = { version = "1.5", default-features = false, features = ["serde"] } tokio = { version = "1", features = ["full"] } neon-lib = { path = "../lib" } +build-info = "0.0.37" + +[build-dependencies] +build-info-build = "0.0.37" diff --git a/evm_loader/cli/build.rs b/evm_loader/cli/build.rs new file mode 100644 index 000000000..d36778f60 --- /dev/null +++ b/evm_loader/cli/build.rs @@ -0,0 +1,3 @@ +fn main() { + build_info_build::build_script(); +} diff --git a/evm_loader/cli/src/build_info.rs b/evm_loader/cli/src/build_info.rs new file mode 100644 index 000000000..85f42da0d --- /dev/null +++ b/evm_loader/cli/src/build_info.rs @@ -0,0 +1,7 @@ +use neon_lib::build_info_common::SlimBuildInfo; + +build_info::build_info!(fn build_info); + +pub fn get_build_info() -> SlimBuildInfo { + build_info().into() +} diff --git a/evm_loader/cli/src/config.rs b/evm_loader/cli/src/config.rs index 4e81ee87e..2db2704a7 100644 --- a/evm_loader/cli/src/config.rs +++ b/evm_loader/cli/src/config.rs @@ -5,8 +5,8 @@ use solana_clap_utils::{ input_parsers::pubkey_of, input_validators::normalize_to_url_if_moniker, keypair::keypair_from_path, }; -use solana_sdk::commitment_config::CommitmentConfig; -use std::{str::FromStr, sync::Arc}; +use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair, signer::Signer}; +use std::str::FromStr; /// # Panics /// # Errors @@ -28,11 +28,7 @@ pub fn create(options: &ArgMatches) -> Result { .unwrap_or(&solana_cli_config.json_rpc_url), ); - let evm_loader = if let Some(value) = pubkey_of(options, "evm_loader") { - value - } else { - return Err(NeonError::EvmLoaderNotSpecified); - }; + let evm_loader = pubkey_of(options, "evm_loader").ok_or(NeonError::EvmLoaderNotSpecified)?; let keypair_path: String = options .value_of("keypair") @@ -47,8 +43,16 @@ pub fn create(options: &ArgMatches) -> Result { "fee_payer", true, ) - .ok() - .map(Arc::new); + .ok(); + + let key_for_config = if let Some(key_for_config) = pubkey_of(options, "solana_key_for_config") { + key_for_config + } else { + fee_payer + .as_ref() + .map(Keypair::pubkey) + .ok_or(NeonError::SolanaKeyForConfigNotSpecified)? + }; let db_config = options .value_of("db_config") @@ -56,6 +60,7 @@ pub fn create(options: &ArgMatches) -> Result { Ok(Config { evm_loader, + key_for_config, fee_payer, commitment, solana_cli_config, diff --git a/evm_loader/cli/src/context.rs b/evm_loader/cli/src/context.rs deleted file mode 100644 index 2933f5ca8..000000000 --- a/evm_loader/cli/src/context.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::sync::Arc; - -use clap::ArgMatches; -use hex::FromHex; -use neon_lib::context::truncate_0x; -pub use neon_lib::context::*; -use neon_lib::rpc; -use neon_lib::rpc::CallDbClient; -use neon_lib::rpc::TrxDbClient; -use neon_lib::Config; -use neon_lib::NeonError; -use solana_client::nonblocking::rpc_client::RpcClient; - -/// # Errors -pub async fn create_from_config_and_options<'a>( - options: &'a ArgMatches<'a>, - config: Arc, - slot: &'a Option, -) -> Result { - let (cmd, params) = options.subcommand(); - - let rpc_client: Arc = match (cmd, params) { - ("emulate-hash" | "trace-hash" | "emulate_hash" | "trace_hash", Some(params)) => { - let hash = params.value_of("hash").expect("hash not found"); - let hash = <[u8; 32]>::from_hex(truncate_0x(hash)).expect("hash cast error"); - - Arc::new( - TrxDbClient::new( - config.db_config.as_ref().expect("db-config not found"), - hash, - ) - .await, - ) - } - _ => { - if let Some(slot) = slot { - Arc::new(CallDbClient::new( - config.db_config.as_ref().expect("db-config not found"), - *slot, - )) - } else { - Arc::new(RpcClient::new_with_commitment( - config.json_rpc_url.clone(), - config.commitment, - )) - } - } - }; - - Ok(neon_lib::context::create(rpc_client, config.clone())) -} diff --git a/evm_loader/cli/src/logs.rs b/evm_loader/cli/src/logs.rs index 91c2d2a99..4d43a03cf 100644 --- a/evm_loader/cli/src/logs.rs +++ b/evm_loader/cli/src/logs.rs @@ -29,20 +29,22 @@ pub fn init(options: &ArgMatches) -> Result<(), log::SetLoggerError> { fern::Dispatch::new() .filter(move |metadata| { - let target = metadata.target(); + const MODULES: [&str; 3] = ["neon_cli", "neon_lib", "evm_loader"]; - if target.starts_with("neon_cli") || target.starts_with("evm_loader") { - return metadata.level().to_level_filter() <= log_level; + let target = metadata.target(); + for module in MODULES { + if target.starts_with(module) { + return metadata.level().to_level_filter() <= log_level; + } } - metadata.level() <= log::Level::Warn + metadata.level() <= log::Level::Error }) .chain(fern::Output::call(|record| { let file: &str = record.file().unwrap_or("undefined"); let line: u32 = record.line().unwrap_or(0); - let mut context = CONTEXT.lock().unwrap(); - context.push(LogRecord { + CONTEXT.lock().unwrap().push(LogRecord { message: record.args().to_string(), source: format!("{file}:{line}"), level: record.metadata().level().as_str(), diff --git a/evm_loader/cli/src/main.rs b/evm_loader/cli/src/main.rs index 1a8814f3c..95db4f099 100644 --- a/evm_loader/cli/src/main.rs +++ b/evm_loader/cli/src/main.rs @@ -1,57 +1,169 @@ #![deny(warnings)] -#![deny(clippy::all, clippy::pedantic)] +#![deny(clippy::all, clippy::pedantic, clippy::nursery)] +#![allow(clippy::future_not_send)] +#![allow(clippy::module_name_repetitions)] +mod build_info; mod config; -mod context; mod logs; mod program_options; use neon_lib::{ commands::{ - cancel_trx, collect_treasury, create_ether_account, deposit, emulate, - get_ether_account_data, get_neon_elf, get_neon_elf::CachedElfParams, get_storage_at, - init_environment, trace, + collect_treasury, emulate, get_balance, get_config, get_contract, get_holder, get_neon_elf, + get_storage_at, init_environment, trace, }, - errors, rpc, types, + rpc::CloneRpcClient, + types::{BalanceAddress, EmulateRequest}, + Config, }; use clap::ArgMatches; -pub use config::Config; -pub use context::Context; use std::io::Read; use ethnum::U256; +use log::debug; use serde_json::json; -use solana_clap_utils::input_parsers::{pubkey_of, value_of, values_of}; -use solana_client::client_error::{ClientError, ClientErrorKind}; -use solana_client::nonblocking::rpc_client::RpcClient; -use solana_sdk::pubkey::Pubkey; -use std::str::FromStr; -use std::sync::Arc; +use solana_clap_utils::input_parsers::{pubkey_of, value_of}; use tokio::time::Instant; -use crate::{ - errors::NeonError, - rpc::Rpc, - types::{ - trace::{TraceCallConfig, TraceConfig}, - IndexerDb, TraceNextBlockParams, TransactionHashParams, TransactionParams, TxParams, - }, -}; +use crate::build_info::get_build_info; use evm_loader::types::Address; +use neon_lib::errors::NeonError; +use neon_lib::rpc::{CallDbClient, RpcEnum}; +use neon_lib::tracing::tracers::TracerTypeEnum; +use neon_lib::types::TracerDb; +use solana_clap_utils::keypair::signer_from_path; +use solana_sdk::signature::Signer; type NeonCliResult = Result; -async fn run<'a>(options: &'a ArgMatches<'a>) -> NeonCliResult { +#[allow(clippy::too_many_lines)] +async fn run(options: &ArgMatches<'_>) -> NeonCliResult { + let config = &config::create(options)?; + + match options.subcommand() { + ("emulate", Some(_)) => { + let rpc = build_rpc(options, config).await?; + + let request = read_tx_from_stdin()?; + emulate::execute(&rpc, config.evm_loader, request, None::) + .await + .map(|(result, _)| json!(result)) + } + ("trace", Some(_)) => { + let rpc = build_rpc(options, config).await?; + + let request = read_tx_from_stdin()?; + trace::trace_transaction(&rpc, config.evm_loader, request) + .await + .map(|trace| json!(trace)) + } + ("get-ether-account-data", Some(params)) => { + let rpc = build_rpc(options, config).await?; + + let address = address_of(params, "ether").unwrap(); + let chain_id = value_of(params, "chain_id").unwrap(); + + let account = BalanceAddress { address, chain_id }; + let accounts = std::slice::from_ref(&account); + + get_balance::execute(&rpc, &config.evm_loader, accounts) + .await + .map(|result| json!(result)) + } + ("get-contract-account-data", Some(params)) => { + let rpc = build_rpc(options, config).await?; + + let account = address_of(params, "address").unwrap(); + let accounts = std::slice::from_ref(&account); + + get_contract::execute(&rpc, &config.evm_loader, accounts) + .await + .map(|result| json!(result)) + } + ("get-holder-account-data", Some(params)) => { + let rpc = build_rpc(options, config).await?; + + let account = pubkey_of(params, "account").unwrap(); + + get_holder::execute(&rpc, &config.evm_loader, account) + .await + .map(|result| json!(result)) + } + ("neon-elf-params", Some(params)) => { + let rpc = build_rpc(options, config).await?; + + let program_location = params.value_of("program_location"); + get_neon_elf::execute(config, &rpc, program_location) + .await + .map(|result| json!(result)) + } + ("collect-treasury", Some(_)) => { + let rpc_client = CloneRpcClient::new_from_config(config); + let signer = build_signer(config)?; + + collect_treasury::execute(config, &rpc_client, &*signer) + .await + .map(|result| json!(result)) + } + ("init-environment", Some(params)) => { + let rpc_client = CloneRpcClient::new_from_config(config); + let signer = build_signer(config)?; + + let file = params.value_of("file"); + let send_trx = params.is_present("send-trx"); + let force = params.is_present("force"); + let keys_dir = params.value_of("keys-dir"); + + init_environment::execute( + config, + &rpc_client, + &*signer, + send_trx, + force, + keys_dir, + file, + ) + .await + .map(|result| json!(result)) + } + ("get-storage-at", Some(params)) => { + let rpc = build_rpc(options, config).await?; + + let contract_id = address_of(params, "contract_id").expect("contract_it parse error"); + let index = u256_of(params, "index").expect("index parse error"); + + get_storage_at::execute(&rpc, &config.evm_loader, contract_id, index) + .await + .map(|hash| json!(hex::encode(hash.0))) + } + ("config", Some(_)) => { + let rpc = build_rpc(options, config).await?; + + get_config::execute(&rpc, config.evm_loader) + .await + .map(|result| json!(result)) + } + _ => unreachable!(), + } +} + +async fn build_rpc(options: &ArgMatches<'_>, config: &Config) -> Result { let slot: Option = options .value_of("slot") .map(|slot_str| slot_str.parse().expect("slot parse error")); - let (cmd, params) = options.subcommand(); - let config = Arc::new(config::create(options)?); - let context: Context = - context::create_from_config_and_options(options, config.clone(), &slot).await?; - execute(cmd, params, &config, &context, slot).await + Ok(if let Some(slot) = slot { + let db_config = config + .db_config + .clone() + .ok_or(NeonError::LoadingDBConfigError)?; + let tracer_db = TracerDb::from_config(&db_config).await; + RpcEnum::CallDbClient(CallDbClient::new(tracer_db, slot, None).await?) + } else { + RpcEnum::CloneRpcClient(CloneRpcClient::new_from_config(config)) + }) } fn print_result(result: &NeonCliResult) { @@ -61,12 +173,12 @@ fn print_result(result: &NeonCliResult) { }; let result = match result { - Ok(value) => serde_json::json!({ + Ok(value) => json!({ "result": "success", "value": value, "logs": logs }), - Err(e) => serde_json::json!({ + Err(e) => json!({ "result": "error", "error": e.to_string(), "logs": logs @@ -76,7 +188,7 @@ fn print_result(result: &NeonCliResult) { println!("{}", serde_json::to_string_pretty(&result).unwrap()); } -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() { let time_start = Instant::now(); @@ -88,313 +200,23 @@ async fn main() { print_result(&Err(NeonError::Panic(message))); })); + debug!("{}", get_build_info()); + let result = run(&options).await; let execution_time = Instant::now().duration_since(time_start); log::info!("execution time: {} sec", execution_time.as_secs_f64()); print_result(&result); if let Err(e) = result { - std::process::exit(e.error_code()); + std::process::exit(e.error_code().try_into().unwrap()); }; } -#[allow(clippy::too_many_lines)] -async fn execute<'a>( - cmd: &str, - params: Option<&'a ArgMatches<'a>>, - config: &'a Config, - context: &'a Context, - slot: Option, -) -> NeonCliResult { - match (cmd, params) { - ("emulate", Some(params)) => { - let (tx, trace_call_config) = parse_tx(params); - let (token, chain, steps, accounts, solana_accounts) = - parse_tx_params(config, context, params).await; - emulate::execute( - context.rpc_client.as_ref(), - config.evm_loader, - tx, - token, - chain, - steps, - config.commitment, - &accounts, - &solana_accounts, - trace_call_config, - ) - .await - .map(|result| json!(result)) - } - ("emulate-hash", Some(params)) => { - let (tx, trace_config) = parse_tx_hash(context.rpc_client.as_ref()).await; - let (token, chain, steps, accounts, solana_accounts) = - parse_tx_params(config, context, params).await; - emulate::execute( - context.rpc_client.as_ref(), - config.evm_loader, - tx, - token, - chain, - steps, - config.commitment, - &accounts, - &solana_accounts, - trace_config.into(), - ) - .await - .map(|result| json!(result)) - } - ("trace", Some(params)) => { - let (tx, trace_call_config) = parse_tx(params); - let (token, chain, steps, accounts, solana_accounts) = - parse_tx_params(config, context, params).await; - trace::trace_transaction( - context.rpc_client.as_ref(), - config.evm_loader, - tx, - token, - chain, - steps, - config.commitment, - &accounts, - &solana_accounts, - trace_call_config, - ) - .await - .map(|trace| json!(trace)) - } - ("trace-hash", Some(params)) => { - let (tx, trace_config) = parse_tx_hash(context.rpc_client.as_ref()).await; - let (token, chain, steps, accounts, solana_accounts) = - parse_tx_params(config, context, params).await; - trace::trace_transaction( - context.rpc_client.as_ref(), - config.evm_loader, - tx, - token, - chain, - steps, - config.commitment, - &accounts, - &solana_accounts, - trace_config.into(), - ) - .await - .map(|trace| json!(trace)) - } - ("trace-next-block", Some(params)) => { - let slot = slot.expect("SLOT argument is not provided"); - let trace_block_params: Option = read_from_stdin() - .unwrap_or_else(|err| { - panic!("Unable to parse `TraceBlockBySlotParams` from STDIN, error: {err:?}") - }); - let trace_config = trace_block_params - .map(|params| params.trace_config.unwrap_or_default()) - .unwrap_or_default(); - let (token, chain, steps, accounts, solana_accounts) = - parse_tx_params(config, context, params).await; - let indexer_db = - IndexerDb::new(config.db_config.as_ref().expect("db-config is required")).await; - let transactions = indexer_db - .get_block_transactions(slot + 1) - .await - .map_err(|e| { - ClientError::from(ClientErrorKind::Custom(format!( - "get_block_transactions error: {e}" - ))) - })?; - trace::trace_block( - context.rpc_client.as_ref(), - config.evm_loader, - transactions, - token, - chain, - steps, - config.commitment, - &accounts, - &solana_accounts, - &trace_config, - ) - .await - .map(|traces| json!(traces)) - } - ("create-ether-account", Some(params)) => { - let ether = address_of(params, "ether").expect("ether parse error"); - let rpc_client = context - .rpc_client - .as_any() - .downcast_ref::() - .expect("cast to solana_client::nonblocking::rpc_client::RpcClient error"); - create_ether_account::execute( - rpc_client, - config.evm_loader, - context.signer()?.as_ref(), - ðer, - ) - .await - .map(|result| json!(result)) - } - ("deposit", Some(params)) => { - let rpc_client = context - .rpc_client - .as_any() - .downcast_ref::() - .expect("cast to solana_client::nonblocking::rpc_client::RpcClient error"); - let amount = value_of(params, "amount").expect("amount parse error"); - let ether = address_of(params, "ether").expect("ether parse error"); - deposit::execute( - rpc_client, - config.evm_loader, - context.signer()?.as_ref(), - amount, - ðer, - ) - .await - .map(|result| json!(result)) - } - ("get-ether-account-data", Some(params)) => { - let ether = address_of(params, "ether").expect("ether parse error"); - get_ether_account_data::execute(context.rpc_client.as_ref(), &config.evm_loader, ðer) - .await - .map(|result| json!(result)) - } - ("cancel-trx", Some(params)) => { - let storage_account = - pubkey_of(params, "storage_account").expect("storage_account parse error"); - cancel_trx::execute( - context.rpc_client.as_ref(), - context.signer()?.as_ref(), - config.evm_loader, - &storage_account, - ) - .await - .map(|result| json!(result)) - } - ("neon-elf-params", Some(params)) => { - let program_location = params.value_of("program_location"); - get_neon_elf::execute(config, context, program_location) - .await - .map(|result| json!(result)) - } - ("collect-treasury", Some(_)) => collect_treasury::execute(config, context) - .await - .map(|result| json!(result)), - ("init-environment", Some(params)) => { - let file = params.value_of("file"); - let send_trx = params.is_present("send-trx"); - let force = params.is_present("force"); - let keys_dir = params.value_of("keys-dir"); - init_environment::execute(config, context, send_trx, force, keys_dir, file) - .await - .map(|result| json!(result)) - } - ("get-storage-at", Some(params)) => { - let contract_id = address_of(params, "contract_id").expect("contract_it parse error"); - let index = u256_of(params, "index").expect("index parse error"); - get_storage_at::execute( - context.rpc_client.as_ref(), - &config.evm_loader, - contract_id, - &index, - ) - .await - .map(|hash| json!(hex::encode(hash.0))) - } - _ => unreachable!(), - } -} - -fn parse_tx(params: &ArgMatches) -> (TxParams, TraceCallConfig) { - let from = address_of(params, "sender").expect("sender parse error"); - let to = address_or_deploy_of(params, "contract"); - let transaction_params: Option = read_from_stdin().unwrap_or_else(|err| { - panic!("Unable to parse `TransactionParams` from STDIN, error: {err:?}") - }); - let (data, trace_config) = transaction_params - .map(|params| { - ( - params.data.map(Into::into), - params.trace_config.unwrap_or_default(), - ) - }) - .unwrap_or_default(); - let value = u256_of(params, "value"); - let gas_limit = u256_of(params, "gas_limit"); - - let tx_params = TxParams { - nonce: None, - from, - to, - data, - value, - gas_limit, - }; +fn read_tx_from_stdin() -> Result { + let mut stdin_buffer = String::new(); + std::io::stdin().read_to_string(&mut stdin_buffer)?; - (tx_params, trace_config) -} - -async fn parse_tx_hash(rpc_client: &dyn Rpc) -> (TxParams, TraceConfig) { - let tx = rpc_client.get_transaction_data().await.unwrap(); - let transaction_params: Option = - read_from_stdin().unwrap_or_else(|err| { - panic!("Unable to parse `TransactionHashParams` from STDIN, error: {err:?}") - }); - - let trace_config = transaction_params - .map(|params| params.trace_config.unwrap_or_default()) - .unwrap_or_default(); - - (tx, trace_config) -} - -pub async fn parse_tx_params<'a>( - config: &Config, - context: &Context, - params: &'a ArgMatches<'a>, -) -> (Pubkey, u64, u64, Vec
, Vec) { - // Read ELF params only if token_mint or chain_id is not set. - let mut token = pubkey_of(params, "token_mint"); - let mut chain = value_of(params, "chain_id"); - if token.is_none() || chain.is_none() { - let cached_elf_params = CachedElfParams::new(config, context).await; - token = token.or_else(|| { - Some( - Pubkey::from_str( - cached_elf_params - .get("NEON_TOKEN_MINT") - .expect("NEON_TOKEN_MINT load error"), - ) - .expect("NEON_TOKEN_MINT Pubkey ctor error "), - ) - }); - chain = chain.or_else(|| { - Some( - u64::from_str( - cached_elf_params - .get("NEON_CHAIN_ID") - .expect("NEON_CHAIN_ID load error"), - ) - .expect("NEON_CHAIN_ID u64 ctor error"), - ) - }); - } - let token = token.expect("token_mint get error"); - let chain = chain.expect("chain_id get error"); - let max_steps = - value_of::(params, "max_steps_to_execute").expect("max_steps_to_execute parse error"); - - let accounts = values_of::
(params, "cached_accounts").unwrap_or_default(); - let solana_accounts = values_of::(params, "solana_accounts").unwrap_or_default(); - - (token, chain, max_steps, accounts, solana_accounts) -} - -fn address_or_deploy_of(matches: &ArgMatches<'_>, name: &str) -> Option
{ - if matches.value_of(name) == Some("deploy") { - return None; - } - address_of(matches, name) + serde_json::from_str(&stdin_buffer).map_err(NeonError::from) } fn address_of(matches: &ArgMatches<'_>, name: &str) -> Option
{ @@ -413,13 +235,17 @@ fn u256_of(matches: &ArgMatches<'_>, name: &str) -> Option { }) } -fn read_from_stdin() -> serde_json::Result> { - let mut stdin = String::new(); - std::io::stdin() - .read_to_string(&mut stdin) - .map_err(serde_json::Error::io)?; - if stdin.trim().is_empty() { - return Ok(None); - } - serde_json::from_str(&stdin).map(Some) +/// # Errors +fn build_signer(config: &Config) -> Result, NeonError> { + let mut wallet_manager = None; + + let signer = signer_from_path( + &ArgMatches::default(), + &config.keypair_path, + "keypair", + &mut wallet_manager, + ) + .map_err(|_| NeonError::KeypairNotSpecified)?; + + Ok(signer) } diff --git a/evm_loader/cli/src/program_options.rs b/evm_loader/cli/src/program_options.rs index dbce571ad..05c223de2 100644 --- a/evm_loader/cli/src/program_options.rs +++ b/evm_loader/cli/src/program_options.rs @@ -1,8 +1,6 @@ use clap::{crate_description, crate_name, App, AppSettings, Arg, ArgMatches, SubCommand}; use ethnum::U256; use evm_loader::types::Address; -use hex::FromHex; -use neon_lib::context::truncate_0x; use solana_clap_utils::input_validators::{is_url_or_moniker, is_valid_pubkey}; use std::fmt::Display; @@ -16,19 +14,6 @@ where .map_err(|e| e.to_string()) } -// Return an error if string cannot be parsed as a Address address -fn is_valid_address_or_deploy(string: T) -> Result<(), String> -where - T: AsRef, -{ - if string.as_ref() == "deploy" { - return Ok(()); - } - Address::from_hex(string.as_ref()) - .map(|_| ()) - .map_err(|e| e.to_string()) -} - // Return an error if string cannot be parsed as a U256 integer fn is_valid_u256(string: T) -> Result<(), String> where @@ -44,16 +29,6 @@ where .map_err(|e| e.to_string()) } -fn is_valid_h256(string: T) -> Result<(), String> -where - T: AsRef, -{ - let str = truncate_0x(string.as_ref()); - <[u8; 32]>::from_hex(str) - .map(|_| ()) - .map_err(|e| e.to_string()) -} - fn is_amount(amount: U) -> Result<(), String> where T: std::str::FromStr, @@ -69,128 +44,6 @@ where } } -fn ether_arg<'a, 'b>(idx: u64) -> Arg<'a, 'b> { - Arg::with_name("ether") - .index(idx) - .value_name("ETHER") - .takes_value(true) - .required(true) - .validator(is_valid_address) - .help("Ethereum address") -} - -fn token_mint_arg<'a, 'b>() -> Arg<'a, 'b> { - Arg::with_name("token_mint") - .long("token_mint") - .value_name("TOKEN_MINT") - .takes_value(true) - .global(true) - .validator(is_valid_pubkey) - .help("Pubkey for token_mint") -} - -fn chain_id_arg<'a, 'b>() -> Arg<'a, 'b> { - Arg::with_name("chain_id") - .long("chain_id") - .value_name("CHAIN_ID") - .takes_value(true) - .required(false) - .help("Network chain_id") -} - -fn max_steps_arg<'a, 'b>() -> Arg<'a, 'b> { - Arg::with_name("max_steps_to_execute") - .long("max_steps_to_execute") - .value_name("NUMBER_OF_STEPS") - .takes_value(true) - .required(false) - .default_value("100000") - .help("Maximal number of steps to execute in a single run") -} - -fn trx_params<'a, 'b>(cmd: &'static str, desc: &'static str) -> App<'a, 'b> { - SubCommand::with_name(cmd) - .about(desc) - .arg( - Arg::with_name("sender") - .value_name("SENDER") - .takes_value(true) - .index(1) - .required(true) - .validator(is_valid_address) - .help("The sender of the transaction"), - ) - .arg( - Arg::with_name("contract") - .value_name("CONTRACT") - .takes_value(true) - .index(2) - .required(true) - .validator(is_valid_address_or_deploy) - .help("The contract that executes the transaction or 'deploy'"), - ) - .arg( - Arg::with_name("value") - .value_name("VALUE") - .takes_value(true) - .index(3) - .required(false) - .validator(is_valid_u256) - .help("Transaction value"), - ) - .arg(token_mint_arg()) - .arg(chain_id_arg()) - .arg(max_steps_arg()) - .arg( - Arg::with_name("gas_limit") - .short("G") - .long("gas_limit") - .value_name("GAS_LIMIT") - .takes_value(true) - .required(false) - .validator(is_valid_u256) - .help("Gas limit"), - ) - .arg( - Arg::with_name("cached_accounts") - .value_name("CACHED_ACCOUNTS") - .long("cached_accounts") - .takes_value(true) - .required(false) - .multiple(true) - .validator(is_valid_address) - .help("List of cached account addresses"), - ) - .arg( - Arg::with_name("solana_accounts") - .value_name("SOLANA_ACCOUNTS") - .long("solana_accounts") - .takes_value(true) - .required(false) - .multiple(true) - .validator(is_valid_address) - .help("List of cached solana account pubkeys"), - ) -} - -fn trx_hash<'a, 'b>(cmd: &'static str, alias: &'static str, desc: &'static str) -> App<'a, 'b> { - SubCommand::with_name(cmd) - .alias(alias) - .about(desc) - .arg( - Arg::with_name("hash") - .index(1) - .value_name("hash") - .takes_value(true) - .required(true) - .validator(is_valid_h256) - .help("Neon transaction hash"), - ) - .arg(token_mint_arg()) - .arg(chain_id_arg()) - .arg(max_steps_arg()) -} - #[allow(clippy::too_many_lines)] pub fn parse<'a>() -> ArgMatches<'a> { App::new(crate_name!()) @@ -218,7 +71,7 @@ pub fn parse<'a>() -> ArgMatches<'a> { .long("db_config") .takes_value(true) .global(true) - .help("Configuration file to use Postgress DB") + .help("Configuration file to use Tracer DB") ) .arg( Arg::with_name("slot") @@ -230,15 +83,6 @@ pub fn parse<'a>() -> ArgMatches<'a> { .validator(is_amount::) .help("Slot number to work with archived data"), ) - .arg( - Arg::with_name("verbose") - .short("v") - .long("verbose") - .takes_value(false) - .global(true) - .multiple(true) - .help("Increase message verbosity"), - ) .arg( Arg::with_name("fee_payer") .long("fee-payer") @@ -272,6 +116,16 @@ pub fn parse<'a>() -> ArgMatches<'a> { .validator(is_valid_pubkey) .help("Pubkey for evm_loader contract") ) + .arg( + Arg::with_name("solana_key_for_config") + .long("solana_key_for_config") + .value_name("SOLANA_KEY_FOR_CONFIG") + .takes_value(true) + .global(true) + .required(false) + .validator(is_valid_pubkey) + .help("Operator pubkey, used for config simulation") + ) .arg( Arg::with_name("commitment") .long("commitment") @@ -302,75 +156,62 @@ pub fn parse<'a>() -> ArgMatches<'a> { .help("Logging level"), ) .subcommand( - trx_params( - "emulate", - "Emulation transaction. Additional `TransactionParams` can be provided via STDIN as a JSON object.", - ) - ) - .subcommand( - trx_params( - "trace", - "Emulation transaction to collecting traces. Additional `TransactionParams` can be provided via STDIN as a JSON object.", - ) - ) - .subcommand( - trx_hash( - "emulate-hash", - "emulate_hash", - "Emulation transaction by hash. Additional `TransactionHashParams` can be provided via STDIN as a JSON object.", - ) - ) - .subcommand( - trx_hash( - "trace-hash", - "trace_hash", - "Emulation transaction by hash to collecting traces. Additional `TransactionHashParams` can be provided via STDIN as a JSON object.", - ) + SubCommand::with_name("emulate") + .about("Emulation transaction. Parameters can be provided via STDIN as a JSON object.") ) .subcommand( - SubCommand::with_name("trace-next-block") - .about("Tracing all transactions in the block next to a given block (slot) number. \ - For this command, SLOT argument is required. \ - Additional `TraceNextBlockParams` can be provided via STDIN as a JSON object.") - .arg(token_mint_arg()) - .arg(chain_id_arg()) - .arg(max_steps_arg()) + SubCommand::with_name("trace") + .about("Emulation transaction to collecting traces. Parameters can be provided via STDIN as a JSON object.") ) .subcommand( - SubCommand::with_name("create-ether-account") - .about("Create ethereum account") - .arg(ether_arg(1)) - ) - .subcommand( - SubCommand::with_name("deposit") - .about("Deposit NEONs to ether account") + SubCommand::with_name("get-ether-account-data") + .alias("balance") + .about("Get values stored in associated with given address account data") .arg( - Arg::with_name("amount") - .index(1) - .value_name("AMOUNT") + Arg::with_name("ether") + .value_name("ETHER") .takes_value(true) + .index(1) .required(true) - .validator(is_amount::) - .help("Amount to deposit"), + .validator(is_valid_address) + .help("Ethereum address") + ) + .arg( + Arg::with_name("chain_id") + .long("chain_id") + .value_name("CHAIN_ID") + .takes_value(true) + .index(2) + .required(true) + .help("Network chain_id") ) - .arg(ether_arg(2)) ) .subcommand( - SubCommand::with_name("get-ether-account-data") - .about("Get values stored in associated with given address account data") - .arg(ether_arg(1)) + SubCommand::with_name("get-contract-account-data") + .alias("contract") + .about("Get values stored in associated with given contract") + .arg( + Arg::with_name("address") + .value_name("ADDRESS") + .takes_value(true) + .index(1) + .required(true) + .validator(is_valid_address) + .help("Ethereum address") + ) ) .subcommand( - SubCommand::with_name("cancel-trx") - .about("Cancel NEON transaction") + SubCommand::with_name("get-holder-account-data") + .alias("holder") + .about("Get values stored in a Holder acount") .arg( - Arg::with_name("storage_account") + Arg::with_name("account") .index(1) - .value_name("STORAGE_ACCOUNT") + .value_name("ACCOUNT") .takes_value(true) .required(true) .validator(is_valid_pubkey) - .help("storage account for transaction"), + .help("Public Key"), ) ) .subcommand( @@ -385,6 +226,10 @@ pub fn parse<'a>() -> ArgMatches<'a> { .help("/path/to/evm_loader.so"), ) ) + .subcommand( + SubCommand::with_name("config") + .about("Read configuration parameters from NeonEVM program.") + ) .subcommand( SubCommand::with_name("collect-treasury") .about("Collect lamports from auxiliary treasury accounts to the main treasury balance") diff --git a/evm_loader/deploy-test.sh b/evm_loader/deploy-test.sh deleted file mode 100755 index 07fa8f275..000000000 --- a/evm_loader/deploy-test.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -set -euo pipefail -set +v - -echo "Deploy test..." - -export EVM_LOADER=$(solana address -k evm_loader-keypair.json) -echo EVM_LOADER=${EVM_LOADER} - -echo "Wait for NeonEVM" -wait-for-neon.sh 240 - -ELF_PARAMS=$(neon-cli --evm_loader "$EVM_LOADER" neon-elf-params evm_loader.so) -echo ${ELF_PARAMS} -export $(python3 -c " -import sys, json -for key, value in json.loads(sys.argv[1])['value'].items(): - print(f'{key}={value}') -" "$ELF_PARAMS") - -echo "Create test operator accounts" -create-test-accounts.sh 2 - -py.test -n 6 tests/ - -echo "Deploy test success" -exit 0 diff --git a/evm_loader/docker-compose-ci.yml b/evm_loader/docker-compose-ci.yml deleted file mode 100644 index de41061de..000000000 --- a/evm_loader/docker-compose-ci.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: "2.1" - -services: - solana: - image: ${EVM_LOADER_IMAGE} - environment: - - RUST_LOG=solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=debug,solana_bpf_loader=debug,solana_rbpf=debug - - SOLANA_URL=http://solana:8899 - hostname: solana - ports: - - 8899 - expose: - - "8899" - entrypoint: - /opt/solana/bin/solana-run-neon.sh diff --git a/evm_loader/docker-compose-test.yml b/evm_loader/docker-compose-test.yml deleted file mode 100644 index d9ce3d0cd..000000000 --- a/evm_loader/docker-compose-test.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: "2.1" - -services: - solana: - container_name: solana - image: ${EVM_LOADER_IMAGE} - environment: - - RUST_LOG=solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=debug,solana_bpf_loader=debug,solana_rbpf=debug - - SOLANA_URL=http://127.0.0.1:8899 - hostname: solana - ports: - - 8899:8899 - - 9900:9900 - - 8900:8900 - - 8003:8003/udp - expose: - - "8899" - - "9900" - - "8900" - - "8003/udp" - entrypoint: - /opt/solana/bin/solana-run-neon.sh - -# proxy: -# container_name: proxy -# image: proxy.py:latest -# hostname: proxy -# environment: -# - SOLANA_URL=http://solana:8899 -# ports: -# - 9090:9090 -# expose: -# - "9090" - -networks: - default: - name: evm_loader-deploy_test-net \ No newline at end of file diff --git a/evm_loader/lib-interface/.gitignore b/evm_loader/lib-interface/.gitignore new file mode 100644 index 000000000..2f7896d1d --- /dev/null +++ b/evm_loader/lib-interface/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/evm_loader/lib-interface/Cargo.lock b/evm_loader/lib-interface/Cargo.lock new file mode 100644 index 000000000..7c3935937 --- /dev/null +++ b/evm_loader/lib-interface/Cargo.lock @@ -0,0 +1,474 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "abi_stable" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f69d9465d88d24382d43fa68335a92fe9d3c53a918549c693403ed9a85eff50" +dependencies = [ + "abi_stable_derive", + "abi_stable_shared", + "const_panic", + "core_extensions", + "crossbeam-channel", + "generational-arena", + "libloading", + "lock_api", + "parking_lot", + "paste", + "repr_offset", + "rustc_version", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "abi_stable_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aecd3efa5a5294f5c67913d45f985ccb382b3c93327581529610eeecdf4821a" +dependencies = [ + "abi_stable_shared", + "as_derive_utils", + "core_extensions", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", + "typed-arena", +] + +[[package]] +name = "abi_stable_shared" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b5df7688c123e63f4d4d649cba63f2967ba7f7861b1664fca3f77d3dad2b63" +dependencies = [ + "core_extensions", +] + +[[package]] +name = "as_derive_utils" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff3c96645900a44cf11941c111bd08a6573b0e2f9f69bc9264b179d8fae753c4" +dependencies = [ + "core_extensions", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-ffi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed5a937a789391ebc1c77d3a15b060e0d100e6d7c483a2af3f250d6b8dc2a23" +dependencies = [ + "abi_stable", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const_panic" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" + +[[package]] +name = "core_extensions" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c71dc07c9721607e7a16108336048ee978c3a8b129294534272e8bac96c0ee" +dependencies = [ + "core_extensions_proc_macros", +] + +[[package]] +name = "core_extensions_proc_macros" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f3b219d28b6e3b4ac87bc1fc522e0803ab22e055da177bff0068c4150c61a6" + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "generational-arena" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "neon-interface" +version = "0.1.0" +dependencies = [ + "abi_stable", + "async-ffi", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + +[[package]] +name = "proc-macro2" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "repr_offset" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1070755bd29dffc19d0971cab794e607839ba2ef4b69a9e6fbc8733c1b72ea" +dependencies = [ + "tstr", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "tstr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca3264971090dec0feef3b455a3c178f02762f7550cf4592991ac64b3be2d7e" +dependencies = [ + "tstr_proc_macros", +] + +[[package]] +name = "tstr_proc_macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78122066b0cb818b8afd08f7ed22f7fdbc3e90815035726f0840d0d26c0747a" + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/evm_loader/lib-interface/Cargo.toml b/evm_loader/lib-interface/Cargo.toml new file mode 100644 index 000000000..a3357cdf5 --- /dev/null +++ b/evm_loader/lib-interface/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "neon-lib-interface" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +abi_stable = "0.11.3" +thiserror = "1" +async-ffi = { version = "0.5.0", features = ["abi_stable"] } +serde = "1.0.204" +serde_json = "1.0.121" diff --git a/evm_loader/lib-interface/src/lib.rs b/evm_loader/lib-interface/src/lib.rs new file mode 100644 index 000000000..0a1781d21 --- /dev/null +++ b/evm_loader/lib-interface/src/lib.rs @@ -0,0 +1,62 @@ +#![deny(warnings)] +#![deny(clippy::all, clippy::nursery)] +#![allow(non_camel_case_types)] + +pub mod types; + +use crate::types::RNeonEVMLibResult; +use std::{collections::HashMap, path::Path}; +use thiserror::Error; + +use abi_stable::{ + library::{LibraryError, RootModule}, + package_version_strings, + std_types::{RStr, RString}, + StableAbi, +}; + +#[repr(C)] +#[derive(StableAbi)] +#[sabi(kind(Prefix(prefix_ref = NeonEVMLib_Ref)))] +#[sabi(missing_field(panic))] +pub struct NeonEVMLib { + pub hash: extern "C" fn() -> RString, + pub get_version: extern "C" fn() -> RString, + pub get_build_info: extern "C" fn() -> RString, + + pub invoke: for<'a> extern "C" fn(RStr<'a>, RStr<'a>) -> RNeonEVMLibResult<'a>, +} + +#[allow(clippy::use_self)] +impl RootModule for NeonEVMLib_Ref { + abi_stable::declare_root_module_statics! {NeonEVMLib_Ref} + + const BASE_NAME: &'static str = "neon-lib-interface"; + const NAME: &'static str = "neon-lib-interface"; + const VERSION_STRINGS: abi_stable::sabi_types::VersionStrings = package_version_strings!(); +} + +#[derive(Error, Debug)] +pub enum NeonEVMLibLoadError { + #[error("abi_stable library error")] + LibraryError(#[from] LibraryError), + #[error("IO error")] + IoError(#[from] std::io::Error), +} + +pub fn load_libraries

( + directory: P, +) -> Result, NeonEVMLibLoadError> +where + P: AsRef, +{ + let paths = std::fs::read_dir(directory)?; + let mut result = HashMap::new(); + for path in paths { + let lib = NeonEVMLib_Ref::load_from_file(&path?.path())?; + let hash = lib.hash()(); + + result.insert(hash.into_string(), lib); + } + Ok(result) +} diff --git a/evm_loader/lib-interface/src/types.rs b/evm_loader/lib-interface/src/types.rs new file mode 100644 index 000000000..c6f8f22fe --- /dev/null +++ b/evm_loader/lib-interface/src/types.rs @@ -0,0 +1,12 @@ +use abi_stable::std_types::{RResult, RString}; +use async_ffi::LocalBorrowingFfiFuture; +use serde::{Deserialize, Serialize}; + +pub type RNeonEVMLibResult<'a> = LocalBorrowingFfiFuture<'a, RResult>; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NeonEVMLibError { + pub code: u32, + pub message: String, + pub data: Option, +} diff --git a/evm_loader/lib/Cargo.toml b/evm_loader/lib/Cargo.toml index 43c8ca4d7..7866135ed 100644 --- a/evm_loader/lib/Cargo.toml +++ b/evm_loader/lib/Cargo.toml @@ -1,35 +1,63 @@ [package] name = "neon-lib" -version = "0.1.0" +version = "1.15.0-dev" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] thiserror = "1.0" -bincode = "1.3.1" -evm-loader = { path = "../program", default_features = false, features = ["log", "tracing"] } -solana-sdk = "=1.14.20" -solana-client = "=1.14.20" -solana-clap-utils = "=1.14.20" -solana-cli-config = "=1.14.20" -solana-cli = "=1.14.20" -solana-transaction-status = "=1.14.20" -spl-token = { version = "~3.5", default_features = false, features = ["no-entrypoint"] } -spl-associated-token-account = { version = "~1.1", default_features = false, features = ["no-entrypoint"] } -bs58 = "0.4.0" -hex = "0.4.2" -serde = "1.0.147" -log = "0.4.17" +anyhow = "1.0" +bincode = "1.3.3" +hyper = "0.14" +evm-loader = { path = "../program", default-features = false, features = ["log", "async-trait"] } +jsonrpsee = { version = "0.20", features = ["server", "ws-client", "macros"] } +solana-sdk.workspace = true +solana-client.workspace = true +solana-account-decoder.workspace = true +solana-cli-config.workspace = true +solana-cli.workspace = true +solana-program-runtime.workspace = true +solana-runtime.workspace = true +solana-accounts-db.workspace = true +solana-bpf-loader-program.workspace = true +solana-loader-v4-program.workspace = true +solana-transaction-status.workspace = true +spl-token = { version = "~4.0", default-features = false, features = ["no-entrypoint"] } +spl-associated-token-account = { version = "~2.3", default-features = false, features = ["no-entrypoint"] } +bs58 = "0.5.1" +base64 = "0.22" +hex = { version = "0.4", features = ["serde"] } +serde = "1.0" +serde_json = { version = "1.0", features = ["preserve_order"] } +serde_with = { version = "3.9", features = ["hex"] } +log = "0.4.22" rand = "0.8" -ethnum = { version = "1", default_features = false, features = [ "serde" ] } -goblin = { version = "0.6.0" } -scroll = "0.11.0" +ethnum = { version = "1.5", default-features = false, features = ["serde"] } +goblin = { version = "0.8.2" } +scroll = "0.12.0" tokio = { version = "1", features = ["full"] } -postgres = { version = "0.19", features = ["with-chrono-0_4", "array-impls"] } -tokio-postgres = {version="0.7", features=["with-uuid-0_8"]} -lazy_static = "1.4" -clickhouse = "0.11.5" +clickhouse = "0.12.0" tracing = "0.1" -async-trait = "0.1.68" -axum = "0.6" +async-trait = "0.1.81" +build-info = "0.0.37" +enum_dispatch = "0.3.13" +web3 = "0.19.0" +neon-lib-interface = { path = "../lib-interface" } +abi_stable = "0.11.3" +async-ffi = { version = "0.5.0", features = ["abi_stable"] } +strum = "0.26.3" +strum_macros = "0.26.4" +clap = "2.34.0" +lazy_static = "1.5.0" +elsa = "1.10.0" +arrayref = "0.3.8" + +[dev-dependencies] +hex-literal = "0.4.1" + +[build-dependencies] +build-info-build = "0.0.37" + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/evm_loader/lib/build.rs b/evm_loader/lib/build.rs new file mode 100644 index 000000000..d36778f60 --- /dev/null +++ b/evm_loader/lib/build.rs @@ -0,0 +1,3 @@ +fn main() { + build_info_build::build_script(); +} diff --git a/evm_loader/lib/src/abi/emulate.rs b/evm_loader/lib/src/abi/emulate.rs new file mode 100644 index 000000000..0937068c2 --- /dev/null +++ b/evm_loader/lib/src/abi/emulate.rs @@ -0,0 +1,20 @@ +use super::params_to_neon_error; +use crate::commands::emulate::{self, EmulateResponse}; +use crate::commands::get_config::BuildConfigSimulator; +use crate::config::APIOptions; +use crate::rpc::Rpc; +use crate::tracing::tracers::TracerTypeEnum; +use crate::{types::EmulateApiRequest, NeonResult}; + +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + config: &APIOptions, + params: &str, +) -> NeonResult { + let params: EmulateApiRequest = + serde_json::from_str(params).map_err(|_| params_to_neon_error(params))?; + + emulate::execute(rpc, config.evm_loader, params.body, None::) + .await + .map(|(response, _)| response) +} diff --git a/evm_loader/lib/src/abi/get_balance.rs b/evm_loader/lib/src/abi/get_balance.rs new file mode 100644 index 000000000..5258a1c6b --- /dev/null +++ b/evm_loader/lib/src/abi/get_balance.rs @@ -0,0 +1,17 @@ +use super::params_to_neon_error; +use crate::commands::get_balance::{self, GetBalanceResponse}; +use crate::commands::get_config::BuildConfigSimulator; +use crate::config::APIOptions; +use crate::rpc::Rpc; +use crate::{types::GetBalanceRequest, NeonResult}; + +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + config: &APIOptions, + params: &str, +) -> NeonResult> { + let params: GetBalanceRequest = + serde_json::from_str(params).map_err(|_| params_to_neon_error(params))?; + + get_balance::execute(rpc, &config.evm_loader, ¶ms.account).await +} diff --git a/evm_loader/lib/src/abi/get_config.rs b/evm_loader/lib/src/abi/get_config.rs new file mode 100644 index 000000000..5ea8714e3 --- /dev/null +++ b/evm_loader/lib/src/abi/get_config.rs @@ -0,0 +1,12 @@ +use crate::commands::get_config::{self, BuildConfigSimulator, GetConfigResponse}; +use crate::config::APIOptions; +use crate::rpc::Rpc; +use crate::NeonResult; + +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + config: &APIOptions, + _params: &str, +) -> NeonResult { + get_config::execute(rpc, config.evm_loader).await +} diff --git a/evm_loader/lib/src/abi/get_contract.rs b/evm_loader/lib/src/abi/get_contract.rs new file mode 100644 index 000000000..dc5367596 --- /dev/null +++ b/evm_loader/lib/src/abi/get_contract.rs @@ -0,0 +1,17 @@ +use super::params_to_neon_error; +use crate::commands::get_config::BuildConfigSimulator; +use crate::commands::get_contract::{self, GetContractResponse}; +use crate::config::APIOptions; +use crate::rpc::Rpc; +use crate::{types::GetContractRequest, NeonResult}; + +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + config: &APIOptions, + params: &str, +) -> NeonResult> { + let params: GetContractRequest = + serde_json::from_str(params).map_err(|_| params_to_neon_error(params))?; + + get_contract::execute(rpc, &config.evm_loader, ¶ms.contract).await +} diff --git a/evm_loader/lib/src/abi/get_holder.rs b/evm_loader/lib/src/abi/get_holder.rs new file mode 100644 index 000000000..36a47f27b --- /dev/null +++ b/evm_loader/lib/src/abi/get_holder.rs @@ -0,0 +1,17 @@ +use super::params_to_neon_error; +use crate::commands::get_config::BuildConfigSimulator; +use crate::commands::get_holder::{self, GetHolderResponse}; +use crate::config::APIOptions; +use crate::rpc::Rpc; +use crate::{types::GetHolderRequest, NeonResult}; + +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + config: &APIOptions, + params: &str, +) -> NeonResult { + let params: GetHolderRequest = + serde_json::from_str(params).map_err(|_| params_to_neon_error(params))?; + + get_holder::execute(rpc, &config.evm_loader, params.pubkey).await +} diff --git a/evm_loader/lib/src/abi/get_storage_at.rs b/evm_loader/lib/src/abi/get_storage_at.rs new file mode 100644 index 000000000..2a0bf51f1 --- /dev/null +++ b/evm_loader/lib/src/abi/get_storage_at.rs @@ -0,0 +1,17 @@ +use super::params_to_neon_error; +use crate::commands::get_config::BuildConfigSimulator; +use crate::commands::get_storage_at::{self, GetStorageAtReturn}; +use crate::config::APIOptions; +use crate::rpc::Rpc; +use crate::{types::GetStorageAtRequest, NeonResult}; + +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + config: &APIOptions, + params: &str, +) -> NeonResult { + let params: GetStorageAtRequest = + serde_json::from_str(params).map_err(|_| params_to_neon_error(params))?; + + get_storage_at::execute(rpc, &config.evm_loader, params.contract, params.index).await +} diff --git a/evm_loader/lib/src/abi/mod.rs b/evm_loader/lib/src/abi/mod.rs new file mode 100644 index 000000000..d2adbce5d --- /dev/null +++ b/evm_loader/lib/src/abi/mod.rs @@ -0,0 +1,149 @@ +mod emulate; +mod get_balance; +mod get_config; +mod get_contract; +mod get_holder; +mod get_storage_at; +mod simulate_solana; +pub mod state; +mod trace; + +use crate::{ + abi::state::State, + config::{self, APIOptions}, + types::RequestWithSlot, + LibMethod, NeonError, +}; +use abi_stable::{ + prefix_type::WithMetadata, + sabi_extern_fn, + std_types::{RStr, RString}, +}; +use async_ffi::FutureExt; +use lazy_static::lazy_static; +use neon_lib_interface::{ + types::{NeonEVMLibError, RNeonEVMLibResult}, + NeonEVMLib, +}; +use serde_json::json; + +lazy_static! { + static ref RUNTIME: tokio::runtime::Runtime = tokio::runtime::Runtime::new().unwrap(); + static ref STATE: State = init_state_sync(); +} + +#[must_use] +pub fn init_state_sync() -> State { + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async { State::new(load_config()).await }) +} + +pub const _MODULE_WM_: &WithMetadata = &WithMetadata::new(NeonEVMLib { + hash, + get_version, + get_build_info, + invoke, +}); + +#[sabi_extern_fn] +fn hash() -> RString { + env!("NEON_REVISION").into() +} + +#[sabi_extern_fn] +fn get_version() -> RString { + env!("CARGO_PKG_VERSION").into() +} + +#[sabi_extern_fn] +fn get_build_info() -> RString { + json!(crate::build_info::get_build_info()) + .to_string() + .into() +} + +#[sabi_extern_fn] +fn invoke<'a>(method: RStr<'a>, params: RStr<'a>) -> RNeonEVMLibResult<'a> { + async move { + // Needed for tokio::task::spawn_blocking using thread local storage inside dynamic library + // since dynamic library and executable have different thread local storage namespaces + let _guard = RUNTIME.enter(); + + dispatch(method.as_str(), params.as_str()) + .await + .map(RString::from) + .map_err(neon_error_to_rstring) + .into() + } + .into_local_ffi() +} + +fn load_config() -> APIOptions { + config::load_api_config_from_environment() +} + +async fn dispatch(method_str: &str, params_str: &str) -> Result { + let method: LibMethod = method_str.parse()?; + let RequestWithSlot { + slot, + tx_index_in_block, + } = match params_str { + "" => RequestWithSlot { + slot: None, + tx_index_in_block: None, + }, + _ => serde_json::from_str(params_str).map_err(|_| params_to_neon_error(params_str))?, + }; + let state = &STATE; + let config = &state.config; + let rpc = state.build_rpc(slot, tx_index_in_block).await?; + + match method { + LibMethod::Emulate => emulate::execute(&rpc, config, params_str) + .await + .map(|v| serde_json::to_string(&v).unwrap()), + LibMethod::GetStorageAt => get_storage_at::execute(&rpc, config, params_str) + .await + .map(|v| serde_json::to_string(&v).unwrap()), + LibMethod::GetBalance => get_balance::execute(&rpc, config, params_str) + .await + .map(|v| serde_json::to_string(&v).unwrap()), + LibMethod::GetConfig => get_config::execute(&rpc, config, params_str) + .await + .map(|v| serde_json::to_string(&v).unwrap()), + LibMethod::GetContract => get_contract::execute(&rpc, config, params_str) + .await + .map(|v| serde_json::to_string(&v).unwrap()), + LibMethod::GetHolder => get_holder::execute(&rpc, config, params_str) + .await + .map(|v| serde_json::to_string(&v).unwrap()), + LibMethod::Trace => trace::execute(&rpc, config, params_str) + .await + .map(|v| serde_json::to_string(&v).unwrap()), + LibMethod::SimulateSolana => simulate_solana::execute(&rpc, config, params_str) + .await + .map(|v| serde_json::to_string(&v).unwrap()), + // _ => Err(NeonError::IncorrectLibMethod), + } +} + +fn params_to_neon_error(params: &str) -> NeonError { + NeonError::EnvironmentError( + crate::commands::init_environment::EnvironmentError::InvalidProgramParameter(params.into()), + ) +} + +fn neon_error_to_neon_lib_error(error: &NeonError) -> NeonEVMLibError { + assert!(error.error_code() != 0); + NeonEVMLibError { + code: error.error_code(), + message: error.to_string(), + data: None, + } +} + +#[allow(clippy::needless_pass_by_value)] +fn neon_error_to_rstring(error: NeonError) -> RString { + RString::from(serde_json::to_string(&neon_error_to_neon_lib_error(&error)).unwrap()) +} diff --git a/evm_loader/lib/src/abi/simulate_solana.rs b/evm_loader/lib/src/abi/simulate_solana.rs new file mode 100644 index 000000000..d11858c02 --- /dev/null +++ b/evm_loader/lib/src/abi/simulate_solana.rs @@ -0,0 +1,17 @@ +use super::params_to_neon_error; +use crate::commands::simulate_solana::{self, SimulateSolanaResponse}; +use crate::config::APIOptions; +use crate::rpc::Rpc; +use crate::types::SimulateSolanaRequest; +use crate::NeonResult; + +pub async fn execute( + rpc: &impl Rpc, + _config: &APIOptions, + params: &str, +) -> NeonResult { + let params: SimulateSolanaRequest = + serde_json::from_str(params).map_err(|_| params_to_neon_error(params))?; + + simulate_solana::execute(rpc, params).await +} diff --git a/evm_loader/lib/src/abi/state.rs b/evm_loader/lib/src/abi/state.rs new file mode 100644 index 000000000..9bf0d25ea --- /dev/null +++ b/evm_loader/lib/src/abi/state.rs @@ -0,0 +1,42 @@ +use crate::config::APIOptions; +use crate::rpc::{CallDbClient, CloneRpcClient, RpcEnum}; +use crate::types::TracerDb; +use crate::NeonError; + +pub struct State { + pub tracer_db: Option, + pub rpc_client: CloneRpcClient, + pub config: APIOptions, +} + +impl State { + #[must_use] + pub async fn new(config: APIOptions) -> Self { + Self { + tracer_db: TracerDb::maybe_from_config(&config.db_config).await, + rpc_client: CloneRpcClient::new_from_api_config(&config), + config, + } + } + + pub async fn build_rpc( + &self, + slot: Option, + tx_index_in_block: Option, + ) -> Result { + Ok(if let Some(slot) = slot { + RpcEnum::CallDbClient( + CallDbClient::new( + self.tracer_db + .clone() + .expect("TracerDB must be configured for CallDbClient"), + slot, + tx_index_in_block, + ) + .await?, + ) + } else { + RpcEnum::CloneRpcClient(self.rpc_client.clone()) + }) + } +} diff --git a/evm_loader/lib/src/abi/trace.rs b/evm_loader/lib/src/abi/trace.rs new file mode 100644 index 000000000..10942a739 --- /dev/null +++ b/evm_loader/lib/src/abi/trace.rs @@ -0,0 +1,20 @@ +use serde_json::Value; + +use super::params_to_neon_error; +use crate::commands::emulate::EmulateResponse; +use crate::commands::get_config::BuildConfigSimulator; +use crate::commands::trace::{self}; +use crate::config::APIOptions; +use crate::rpc::Rpc; +use crate::{types::EmulateApiRequest, NeonResult}; + +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + config: &APIOptions, + params: &str, +) -> NeonResult<(EmulateResponse, Option)> { + let params: EmulateApiRequest = + serde_json::from_str(params).map_err(|_| params_to_neon_error(params))?; + + trace::trace_transaction(rpc, config.evm_loader, params.body).await +} diff --git a/evm_loader/lib/src/account_data.rs b/evm_loader/lib/src/account_data.rs new file mode 100644 index 000000000..b78ac8574 --- /dev/null +++ b/evm_loader/lib/src/account_data.rs @@ -0,0 +1,229 @@ +use std::fmt; + +use solana_sdk::account_info::IntoAccountInfo; +use solana_sdk::entrypoint::MAX_PERMITTED_DATA_INCREASE; +use solana_sdk::system_program; +use solana_sdk::{ + account::{Account, ReadableAccount}, + account_info::AccountInfo, + pubkey::Pubkey, +}; + +pub use evm_loader::account_storage::{AccountStorage, SyncedAccountStorage}; +use evm_loader::solana_program::debug_account_data::debug_account_data; +use serde::{Deserialize, Serialize}; +use serde_with::hex::Hex; +use serde_with::serde_as; + +#[allow(clippy::unsafe_derive_deserialize)] +#[serde_as] +#[derive(Clone, Serialize, Deserialize)] +#[repr(C)] +pub struct AccountData { + original_length: u32, + pub pubkey: Pubkey, + pub lamports: u64, + #[serde_as(as = "Hex")] + data: Vec, + pub owner: Pubkey, + pub executable: bool, + pub rent_epoch: u64, +} + +impl fmt::Debug for AccountData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug_struct = f.debug_struct("AccountData"); + + debug_struct + .field("original_length", &self.original_length) + .field("pubkey", &bs58::encode(&self.pubkey).into_string()) + .field("lamports", &self.lamports) + .field("owner", &bs58::encode(&self.owner).into_string()) + .field("executable", &self.executable) + .field("rent_epoch", &self.rent_epoch) + .field("data_len", &self.data.len()); + + debug_account_data(&self.data, &mut debug_struct); + + debug_struct.finish() + } +} + +impl AccountData { + #[must_use] + pub fn new(pubkey: Pubkey) -> Self { + Self { + original_length: 0, + pubkey, + lamports: 0, + data: vec![0u8; 8 + MAX_PERMITTED_DATA_INCREASE], + owner: system_program::ID, + executable: false, + rent_epoch: 0, + } + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.get_length() == 0 && self.owner == system_program::ID + } + + #[must_use] + pub fn is_busy(&self) -> bool { + self.get_length() != 0 || self.owner != system_program::ID + } + + pub fn new_from_account(pubkey: Pubkey, account: &T) -> Self { + let account_data = account.data(); + let mut data = vec![0u8; account_data.len() + 8 + MAX_PERMITTED_DATA_INCREASE]; + let ptr_length: *mut u64 = data.as_mut_ptr().cast(); + unsafe { *ptr_length = account_data.len() as u64 }; + data[8..8 + account_data.len()].copy_from_slice(account_data); + + Self { + original_length: u32::try_from(account_data.len()).unwrap_or_else(|error| { + println!("Error converting account data length: {error}"); + 0 + }), + pubkey, + lamports: account.lamports(), + data, + owner: *account.owner(), + executable: account.executable(), + rent_epoch: account.rent_epoch(), + } + } + + pub fn expand(&mut self, length: usize) { + let len = u32::try_from(length).unwrap_or_else(|error| { + println!("Error converting account data length: {error}"); + 0 + }); + if self.original_length < len { + self.data + .resize(length + 8 + MAX_PERMITTED_DATA_INCREASE, 0); + self.original_length = u32::try_from(length).unwrap_or_else(|error| { + println!("Error converting account data length: {error}"); + 0 + }); + } + let ptr_length: *mut u64 = self.data.as_mut_ptr().cast(); + unsafe { + if *ptr_length < length as u64 { + *ptr_length = length as u64; + } + } + } + + pub fn reserve(&mut self) { + self.expand(self.get_length()); + } + + pub fn assign(&mut self, owner: Pubkey) -> evm_loader::error::Result<()> { + if self.owner != system_program::ID { + return Err(evm_loader::error::Error::AccountAlreadyInitialized( + self.pubkey, + )); + } + self.owner = owner; + Ok(()) + } + + #[must_use] + pub fn data(&self) -> &[u8] { + let length = self.get_length(); + &self.data[8..8 + length] + } + + pub fn data_mut(&mut self) -> &mut [u8] { + let length = self.get_length(); + &mut self.data[8..8 + length] + } + + #[must_use] + pub fn get_length(&self) -> usize { + let ptr_length: *const u64 = self.data.as_ptr().cast(); + usize::try_from(unsafe { *ptr_length }).unwrap_or(0) + } + + fn get(&mut self) -> (&Pubkey, &mut u64, &mut [u8], &Pubkey, bool, u64) { + let length = self.get_length(); + ( + &self.pubkey, + &mut self.lamports, + &mut self.data[8..8 + length], + &self.owner, + self.executable, + self.rent_epoch, + ) + } +} + +impl<'a> IntoAccountInfo<'a> for &'a mut AccountData { + fn into_account_info(self) -> AccountInfo<'a> { + let (pubkey, lamports, data, owner, executable, rent_epoch) = self.get(); + + AccountInfo::new( + pubkey, false, false, lamports, data, owner, executable, rent_epoch, + ) + } +} + +impl<'a> From<&'a AccountData> for Account { + fn from(val: &'a AccountData) -> Self { + Self { + lamports: val.lamports, + data: val.data().to_vec(), + owner: val.owner, + executable: val.executable, + rent_epoch: val.rent_epoch, + } + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use super::*; + + #[tokio::test] + async fn test_account_data() { + let mut account_data = AccountData::new(Pubkey::default()); + let new_owner = Pubkey::from_str("53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io").unwrap(); + let new_size: usize = 10 * 1024; + + { + let account_info = (&mut account_data).into_account_info(); + assert_eq!(account_info.try_data_len().unwrap(), 0); + account_info.realloc(new_size - 1, false).unwrap(); + account_info.assign(&new_owner); + } + + assert_eq!(account_data.get_length(), new_size - 1); + + { + let account_info = (&mut account_data).into_account_info(); + assert_eq!(account_info.try_data_len().unwrap(), new_size - 1); + assert_eq!(account_info.realloc(new_size, false), Ok(())); + assert_eq!( + account_info.realloc(new_size + 1, false), + Err(solana_sdk::program_error::ProgramError::InvalidRealloc) + ); + let mut lamports = account_info.try_borrow_mut_lamports().unwrap(); + **lamports = 10000; + } + + assert_eq!(account_data.get_length(), new_size); + assert_eq!(account_data.owner, new_owner); + assert_eq!(account_data.lamports, 10000); + + { + let account_info = (&mut account_data).into_account_info(); + account_info.realloc(0, false).unwrap(); + account_info.assign(&Pubkey::default()); + } + assert_eq!(account_data.get_length(), 0); + assert_eq!(account_data.owner, Pubkey::default()); + } +} diff --git a/evm_loader/lib/src/account_storage.rs b/evm_loader/lib/src/account_storage.rs index e1a6ff10b..5486ff2ee 100644 --- a/evm_loader/lib/src/account_storage.rs +++ b/evm_loader/lib/src/account_storage.rs @@ -1,507 +1,967 @@ -use std::{cell::RefCell, collections::HashMap, convert::TryInto, rc::Rc}; -use tokio::sync::RwLock; - -use crate::{ - rpc::Rpc, - types::trace::{AccountOverrides, BlockOverrides}, - NeonError, -}; +use crate::account_data::AccountData; +use crate::commands::get_config::{BuildConfigSimulator, ChainInfo}; +use crate::tracing::{AccountOverrides, BlockOverrides}; +use crate::{rpc::Rpc, solana_simulator::SolanaSimulator, NeonError, NeonResult}; +use async_trait::async_trait; +use elsa::FrozenMap; use ethnum::U256; -use evm_loader::account::ether_contract; -use evm_loader::account_storage::{find_slot_hash, AccountOperation, AccountsOperations}; +use evm_loader::account_storage::LogCollector; +pub use evm_loader::account_storage::{AccountStorage, SyncedAccountStorage}; use evm_loader::{ account::{ - ether_storage::EthereumStorageAddress, EthereumAccount, EthereumStorage, - ACCOUNT_SEED_VERSION, + legacy::{LegacyEtherData, LegacyStorageData}, + BalanceAccount, ContractAccount, StorageCell, StorageCellAddress, }, - account_storage::AccountStorage, + account_storage::{find_slot_hash, FAKE_OPERATOR}, config::STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT, - executor::{Action, OwnedAccountInfo}, - gasometer::LAMPORTS_PER_SIGNATURE, - types::Address, + error::Error as EvmLoaderError, + executor::OwnedAccountInfo, + types::{vector::VectorVecExt, Address, Vector}, }; -use log::{debug, error, info, trace, warn}; -use serde::{Deserialize, Serialize}; -use solana_client::client_error; -use solana_sdk::entrypoint::MAX_PERMITTED_DATA_INCREASE; +use log::{debug, info, trace}; use solana_sdk::{ account::Account, - account_info::AccountInfo, - commitment_config::CommitmentConfig, - pubkey, - pubkey::Pubkey, + account_info::{AccountInfo, IntoAccountInfo}, + clock::Clock, + instruction::Instruction, + program_error::ProgramError, + pubkey::{Pubkey, PubkeyError}, rent::Rent, - sysvar::{slot_hashes, Sysvar}, + system_program, + sysvar::slot_hashes, + transaction_context::TransactionReturnData, +}; +use std::collections::{HashMap, HashSet}; +use std::{ + cell::{Ref, RefCell, RefMut}, + convert::TryInto, + rc::Rc, }; +use web3::types::Log; -use crate::types::{block, PubkeyBase58}; - -const FAKE_OPERATOR: Pubkey = pubkey!("neonoperator1111111111111111111111111111111"); - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NeonAccount { - address: Address, - account: PubkeyBase58, - writable: bool, - new: bool, - size: usize, - size_current: usize, - additional_resize_steps: usize, - #[serde(skip)] - data: Option, +#[derive(Default, Clone, Copy)] +pub struct ExecuteStatus { + pub external_solana_call: bool, + pub reverts_before_solana_calls: bool, + pub reverts_after_solana_calls: bool, } -impl NeonAccount { - fn new(address: Address, pubkey: Pubkey, account: Option, writable: bool) -> Self { - if let Some(account) = account { - trace!("Account found {}", address); - - Self { - address, - account: pubkey.into(), - writable, - new: false, - size: account.data.len(), - size_current: account.data.len(), - additional_resize_steps: 0, - data: Some(account), - } - } else { - trace!("Account not found {}", address); - - Self { - address, - account: pubkey.into(), - writable, - new: true, - size: 0, - size_current: 0, - additional_resize_steps: 0, - data: None, - } +#[derive(Debug, Clone)] +pub struct SolanaAccount { + pub pubkey: Pubkey, + pub is_writable: bool, + pub is_legacy: bool, + pub lamports_after_upgrade: Option, +} + +pub type SolanaOverrides = HashMap>; + +trait UpdateLamports<'a> { + fn update_lamports(&mut self, rent: &Rent) { + let required_lamports = rent.minimum_balance(self.required_lamports()); + if self.info().lamports() < required_lamports { + let mut lamports = self.info().lamports.borrow_mut(); + **lamports = required_lamports; } } - - pub async fn rpc_load( - rpc_client: &dyn Rpc, - evm_loader: &Pubkey, - address: Address, - writable: bool, - ) -> Self { - let (key, _) = make_solana_program_address(&address, evm_loader); - info!("get_account_from_solana {address} => {key}"); - - let account = match rpc_client.get_account(&key).await { - Ok(account) => Some(account), - Err(err) => { - error!("rpc_client.get_account {key} error: {err:?}"); - None - } - }; - Self::new(address, key, account, writable) + fn required_lamports(&self) -> usize; + fn info(&self) -> &AccountInfo<'a>; +} +impl<'a> UpdateLamports<'a> for BalanceAccount<'a> { + fn required_lamports(&self) -> usize { + BalanceAccount::required_account_size() + } + fn info(&self) -> &AccountInfo<'a> { + self.info() } } - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SolanaAccount { - pubkey: PubkeyBase58, - is_writable: bool, - #[serde(skip)] - data: Option, +impl<'a> UpdateLamports<'a> for ContractAccount<'a> { + fn required_lamports(&self) -> usize { + ContractAccount::required_account_size(self.code().as_ref()) + } + fn info(&self) -> &AccountInfo<'a> { + self.info() + } +} +impl<'a> UpdateLamports<'a> for StorageCell<'a> { + fn required_lamports(&self) -> usize { + StorageCell::required_account_size(self.cells().len()) + } + fn info(&self) -> &AccountInfo<'a> { + self.info() + } } #[allow(clippy::module_name_repetitions)] -pub struct EmulatorAccountStorage<'a> { - pub accounts: RwLock>, - pub solana_accounts: RwLock>, - rpc_client: &'a dyn Rpc, - evm_loader: Pubkey, +pub struct EmulatorAccountStorage<'rpc, T: Rpc> { + accounts: FrozenMap>>, + call_stack: Vec>>>, + + pub gas: u64, + pub realloc_iterations: u64, + pub execute_status: ExecuteStatus, + rpc: &'rpc T, + program_id: Pubkey, + operator: Pubkey, + chains: Vec, block_number: u64, block_timestamp: i64, - neon_token_mint: Pubkey, - chain_id: u64, - commitment: CommitmentConfig, + timestamp_used: RefCell, + rent: Rent, state_overrides: Option, + accounts_cache: FrozenMap>>, + used_accounts: FrozenMap>>, + return_data: Option, + logs: Vec, + logs_stack: Vec, } -impl<'a> EmulatorAccountStorage<'a> { +impl<'rpc, T: Rpc + BuildConfigSimulator> EmulatorAccountStorage<'rpc, T> { pub async fn new( - rpc_client: &'a dyn Rpc, - evm_loader: Pubkey, - token_mint: Pubkey, - chain_id: u64, - commitment: CommitmentConfig, - block_overrides: &Option, + rpc: &'rpc T, + program_id: Pubkey, + chains: Option>, + block_overrides: Option, state_overrides: Option, - ) -> Result, NeonError> { + solana_overrides: Option, + tx_chain_id: Option, + ) -> Result, NeonError> { trace!("backend::new"); - let block_number = match block_overrides - .as_ref() - .and_then(|overrides| overrides.number) - { - None => rpc_client.get_slot().await?, + let block_number = match block_overrides.as_ref().and_then(|o| o.number) { + None => rpc.get_slot().await?, Some(number) => number, }; - let block_timestamp = match block_overrides - .as_ref() - .and_then(|overrides| overrides.time) - { - None => rpc_client.get_block_time(block_number).await?, + let block_timestamp = match block_overrides.as_ref().and_then(|o| o.time) { + None => rpc.get_block_time(block_number).await?, Some(time) => time, }; - Ok(Self { - accounts: RwLock::new(HashMap::new()), - solana_accounts: RwLock::new(HashMap::new()), - rpc_client, - evm_loader, + let chains = match chains { + None => crate::commands::get_config::read_chains(rpc, program_id).await?, + Some(chains) => chains, + }; + + let rent_account = rpc + .get_account(&solana_sdk::sysvar::rent::id()) + .await? + .ok_or(NeonError::AccountNotFound(solana_sdk::sysvar::rent::id()))?; + + let rent = bincode::deserialize::(&rent_account.data)?; + info!("Rent: {rent:?}"); + + let accounts_cache = FrozenMap::new(); + if let Some(overrides) = solana_overrides { + for (pubkey, account) in overrides { + accounts_cache.insert(pubkey, Box::new(account)); + } + } + let storage = Self { + accounts: FrozenMap::new(), + call_stack: vec![], + program_id, + operator: FAKE_OPERATOR, + chains, + gas: 0, + realloc_iterations: 0, + execute_status: ExecuteStatus::default(), + rpc, block_number, block_timestamp, - neon_token_mint: token_mint, - chain_id, - commitment, + timestamp_used: RefCell::new(false), state_overrides, - }) + rent, + accounts_cache, + used_accounts: FrozenMap::new(), + return_data: None, + logs: vec![], + logs_stack: vec![], + }; + + let target_chain_id = tx_chain_id.unwrap_or_else(|| storage.default_chain_id()); + storage.apply_balance_overrides(target_chain_id).await?; + + Ok(storage) + } + + pub async fn new_from_other( + other: &Self, + block_shift: u64, + timestamp_shift: i64, + tx_chain_id: Option, + ) -> Result, NeonError> { + let storage = Self { + accounts: FrozenMap::new(), + call_stack: vec![], + program_id: other.program_id, + operator: other.operator, + chains: other.chains.clone(), + gas: 0, + realloc_iterations: 0, + execute_status: ExecuteStatus::default(), + rpc: other.rpc, + block_number: other.block_number.saturating_add(block_shift), + block_timestamp: other.block_timestamp.saturating_add(timestamp_shift), + timestamp_used: RefCell::new(false), + rent: other.rent, + state_overrides: other.state_overrides.clone(), + accounts_cache: other.accounts_cache.clone(), + used_accounts: other.used_accounts.clone(), + return_data: None, + logs: other.logs.clone(), + logs_stack: other.logs_stack.clone(), + }; + let target_chain_id = tx_chain_id.unwrap_or_else(|| storage.default_chain_id()); + storage.apply_balance_overrides(target_chain_id).await?; + Ok(storage) } #[allow(clippy::too_many_arguments)] pub async fn with_accounts( - rpc_client: &'a dyn Rpc, - evm_loader: Pubkey, - token_mint: Pubkey, - chain_id: u64, - commitment: CommitmentConfig, - accounts: &[Address], - solana_accounts: &[Pubkey], - block_overrides: &Option, + rpc: &'rpc T, + program_id: Pubkey, + accounts: &[Pubkey], + chains: Option>, + block_overrides: Option, state_overrides: Option, - ) -> Result, NeonError> { + solana_overrides: Option, + tx_chain_id: Option, + ) -> Result, NeonError> { let storage = Self::new( - rpc_client, - evm_loader, - token_mint, - chain_id, - commitment, + rpc, + program_id, + chains, block_overrides, state_overrides, + solana_overrides, + tx_chain_id, ) .await?; - storage - .initialize_cached_accounts(accounts, solana_accounts) - .await; + + storage.download_accounts(accounts).await?; Ok(storage) } +} - pub async fn initialize_cached_accounts( - &self, - addresses: &[Address], - solana_accounts: &[Pubkey], - ) { - let pubkeys: Vec<_> = addresses - .iter() - .map(|address| make_solana_program_address(address, &self.evm_loader).0) - .chain(solana_accounts.iter().copied()) - .collect(); - - if let Ok(accounts) = self.rpc_client.get_multiple_accounts(&pubkeys).await { - let entries = addresses - .iter() - .zip(accounts.iter().take(addresses.len())) - .zip(pubkeys.iter().take(addresses.len())); - let mut accounts_storage = self.accounts.write().await; - for ((&address, account), &pubkey) in entries { - accounts_storage.insert( - address, - NeonAccount::new(address, pubkey, account.clone(), false), - ); - } - - let entries = accounts.iter().skip(addresses.len()).zip(solana_accounts); - let mut solana_accounts_storage = self.solana_accounts.write().await; - for (account, &pubkey) in entries { - solana_accounts_storage.insert( - pubkey, - SolanaAccount { - pubkey: pubkey.into(), - is_writable: false, - data: account.clone(), - }, - ); +impl<'a, T: Rpc> EmulatorAccountStorage<'_, T> { + async fn apply_balance_overrides(&self, target_chain_id: u64) -> NeonResult<()> { + if let Some(state_overrides) = self.state_overrides.as_ref() { + for (address, overrides) in state_overrides { + if overrides.nonce.is_none() && overrides.balance.is_none() { + continue; + } + let mut balance_data = self + .get_balance_account(*address, target_chain_id) + .await? + .borrow_mut(); + let mut balance = self.get_or_create_ethereum_balance( + &mut balance_data, + *address, + target_chain_id, + )?; + if let Some(nonce) = overrides.nonce { + info!("apply nonce overrides {address} -> {nonce}"); + balance.override_nonce_by(nonce); + } + if let Some(expected_balance) = overrides.balance { + info!("apply balance overrides {address} -> {expected_balance}"); + balance.override_balance_by(expected_balance); + } } } + Ok(()) } - pub async fn get_account(&self, pubkey: &Pubkey) -> client_error::Result> { - let mut accounts = self.solana_accounts.write().await; + async fn download_accounts(&self, pubkeys: &[Pubkey]) -> Result<(), NeonError> { + let accounts = self.rpc.get_multiple_accounts(pubkeys).await?; - if let Some(account) = accounts.get(pubkey) { - if let Some(ref data) = account.data { - return Ok(Some(data.clone())); - } + for (key, account) in pubkeys.iter().zip(accounts) { + self.accounts_cache.insert(*key, Box::new(account)); } - let result = self - .rpc_client - .get_account_with_commitment(pubkey, self.commitment) - .await?; + Ok(()) + } - accounts - .entry(*pubkey) - .and_modify(|a| a.data = result.value.clone()) - .or_insert(SolanaAccount { - pubkey: pubkey.into(), - is_writable: false, - data: result.value.clone(), - }); + pub async fn _get_deactivated_solana_features( + &self, + ) -> solana_client::client_error::Result> { + self.rpc.get_deactivated_solana_features().await + } - Ok(result.value) + pub async fn _get_account_from_rpc( + &self, + pubkey: Pubkey, + ) -> solana_client::client_error::Result> { + if pubkey == FAKE_OPERATOR { + return Ok(None); + } + + if let Some(account) = self.accounts_cache.get(&pubkey) { + return Ok(account.as_ref()); + } + + let response = self.rpc.get_account(&pubkey).await?; + let account = self.accounts_cache.insert(pubkey, Box::new(response)); + Ok(account.as_ref()) } - pub async fn get_account_from_solana( - rpc_client: &'a dyn Rpc, - evm_loader: &'a Pubkey, - address: &Address, - ) -> (Pubkey, Option) { - let (solana_address, _solana_nonce) = make_solana_program_address(address, evm_loader); - info!("get_account_from_solana {} => {}", address, solana_address); + pub async fn _get_multiple_accounts_from_rpc( + &self, + pubkeys: &[Pubkey], + ) -> solana_client::client_error::Result>> { + let mut accounts = vec![None; pubkeys.len()]; - if let Ok(acc) = rpc_client.get_account(&solana_address).await { - trace!("Account found"); - trace!("Account data len {}", acc.data.len()); - trace!("Account owner {}", acc.owner); + let mut exists = vec![true; pubkeys.len()]; + let mut missing_keys = Vec::with_capacity(pubkeys.len()); - (solana_address, Some(acc)) - } else { - warn!("Account not found {}", address); + for (i, pubkey) in pubkeys.iter().enumerate() { + if pubkey == &FAKE_OPERATOR { + continue; + } + + let Some(account) = self.accounts_cache.get(pubkey) else { + exists[i] = false; + missing_keys.push(*pubkey); + continue; + }; - (solana_address, None) + accounts[i] = account.as_ref(); } - } - async fn add_ethereum_account(&self, address: &Address, writable: bool) -> bool { - let mut accounts = self.accounts.write().await; + let mut response = self.rpc.get_multiple_accounts(&missing_keys).await?; - if let Some(ref mut account) = accounts.get_mut(address) { - account.writable |= writable; + debug!( + "get_multiple_accounts: missing_keys={:?} response={response:?}", + missing_keys + ); - true - } else { - let account = - NeonAccount::rpc_load(self.rpc_client, &self.evm_loader, *address, writable).await; - accounts.insert(*address, account); + let mut j = 0_usize; + for i in 0..pubkeys.len() { + if exists[i] { + continue; + } + + let pubkey = missing_keys[j]; + let account = response[j].take(); + let account = self.accounts_cache.insert(pubkey, Box::new(account)); + // ^ .insert() returns the reference to the account that was just inserted + + assert_eq!(pubkeys[i], pubkey); + accounts[i] = account.as_ref(); - false + j += 1; } + + Ok(accounts) } - async fn add_solana_account(&self, pubkey: Pubkey, is_writable: bool) { - if solana_sdk::system_program::check_id(&pubkey) { - return; - } + fn mark_account(&self, pubkey: Pubkey, is_writable: bool) { + let mut data = self._get_account_mark(pubkey); + data.is_writable |= is_writable; + } - if pubkey == FAKE_OPERATOR { - return; + fn mark_legacy_account( + &self, + pubkey: Pubkey, + is_writable: bool, + lamports_after_upgrade: Option, + ) { + let mut data = self._get_account_mark(pubkey); + data.is_writable |= is_writable; + data.is_legacy = true; + if lamports_after_upgrade.is_some() { + data.lamports_after_upgrade = lamports_after_upgrade; } + } - let mut solana_accounts = self.solana_accounts.write().await; + fn _get_account_mark(&self, pubkey: Pubkey) -> RefMut<'_, SolanaAccount> { + self.used_accounts + .insert( + pubkey, + Box::new(RefCell::new(SolanaAccount { + pubkey, + is_writable: false, + is_legacy: false, + lamports_after_upgrade: None, + })), + ) + .borrow_mut() + } - let account = SolanaAccount { - pubkey: pubkey.into(), - is_writable, - data: None, - }; - if is_writable { - solana_accounts - .entry(pubkey) - // If account is present in cache ensure the data is not lost - .and_modify(|a| a.is_writable = true) - .or_insert(account); + fn _add_legacy_account( + &self, + info: &AccountInfo<'_>, + ) -> NeonResult<(&RefCell, &RefCell)> { + let legacy = LegacyEtherData::from_account(&self.program_id, info)?; + + let (balance_pubkey, _) = legacy + .address + .find_balance_address(&self.program_id, self.default_chain_id()); + let balance_data = self.add_empty_account(balance_pubkey); + if (legacy.balance > 0) || (legacy.trx_count > 0) { + let mut balance_data = balance_data.borrow_mut(); + let mut balance = self.create_ethereum_balance( + &mut balance_data, + legacy.address, + self.default_chain_id(), + )?; + balance.mint(legacy.balance)?; + balance.increment_nonce_by(legacy.trx_count)?; + self.mark_legacy_account(balance_pubkey, true, Some(balance_data.lamports)); } else { - solana_accounts.entry(pubkey).or_insert(account); + self.mark_legacy_account(balance_pubkey, false, Some(0)); } - } - - pub async fn apply_actions(&self, actions: &[Action]) -> u64 { - info!("apply_actions"); - let mut gas = 0_u64; - let rent = Rent::get().expect("Rent get error"); + let (contract_pubkey, _) = legacy.address.find_solana_address(&self.program_id); + let contract_data = self.add_empty_account(contract_pubkey); + if (legacy.code_size > 0) || (legacy.generation > 0) { + let code = legacy.read_code(info); + let storage = legacy.read_storage(info); + + let mut contract_data = contract_data.borrow_mut(); + let mut contract = self.create_ethereum_contract( + &mut contract_data, + legacy.address, + self.default_chain_id(), + legacy.generation, + &code, + )?; + if !code.is_empty() { + contract.set_storage_multiple_values(0, &storage); + } + self.mark_legacy_account(contract_pubkey, true, Some(contract_data.lamports)); + } else { + // We have to mark account as writable, because we destroy the original legacy account + self.mark_legacy_account(contract_pubkey, true, Some(0)); + } - for action in actions { - #[allow(clippy::match_same_arms)] - match action { - Action::NeonTransfer { - source, - target, - value, - } => { - info!("neon transfer {value} from {source} to {target}"); + Ok((contract_data, balance_data)) + } - self.add_ethereum_account(source, true).await; - self.add_ethereum_account(target, true).await; + async fn _get_contract_generation_limited(&self, address: Address) -> NeonResult> { + let extract_generation = |contract_data: &RefCell| -> NeonResult> { + let mut contract_data = contract_data.borrow_mut(); + if contract_data.is_empty() { + Ok(None) + } else { + let contract = ContractAccount::from_account( + &self.program_id, + contract_data.into_account_info(), + )?; + if contract.code().len() > 0 { + Ok(Some(contract.generation())) + } else { + Ok(None) } - Action::NeonWithdraw { source, value } => { - info!("neon withdraw {value} from {source}"); + } + }; - self.add_ethereum_account(source, true).await; - } - Action::EvmSetStorage { - address, - index, - value, - } => { - info!("set storage {address} -> {index} = {}", hex::encode(value)); - - if *index < U256::from(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT) { - self.add_ethereum_account(address, true).await; - } else { - let (base, _) = address.find_solana_address(self.program_id()); - let storage_account = - EthereumStorageAddress::new(self.program_id(), &base, index); - self.add_solana_account(*storage_account.pubkey(), true) - .await; - - if self.storage(address, index) == [0_u8; 32] { - let metadata_size = EthereumStorage::SIZE; - let element_size = 1 + std::mem::size_of_val(value); - - let cost = rent.minimum_balance(metadata_size + element_size); - gas = gas.saturating_add(cost); + let (pubkey, _) = address.find_solana_address(&self.program_id); + let contract_data = if let Some(contract_data) = self.accounts.get(&pubkey) { + contract_data + } else { + let mut account = self._get_account_from_rpc(pubkey).await?.cloned(); + if let Some(account) = &mut account { + let info = account_info(&pubkey, account); + if *info.owner == self.program_id { + match evm_loader::account::tag(&self.program_id, &info)? { + evm_loader::account::TAG_ACCOUNT_CONTRACT => { + let data = AccountData::new_from_account(pubkey, account); + self.accounts.insert(pubkey, Box::new(RefCell::new(data))) + } + evm_loader::account::legacy::TAG_ACCOUNT_CONTRACT_DEPRECATED => self + ._add_legacy_account(&info) + .map(|(contract, _balance)| contract)?, + _ => { + unimplemented!(); } } + } else { + let account_data = AccountData::new_from_account(pubkey, account); + self.accounts + .insert(pubkey, Box::new(RefCell::new(account_data))) } - Action::EvmIncrementNonce { address } => { - info!("nonce increment {address}"); + } else { + self.add_empty_account(pubkey) + } + }; + self.mark_legacy_account(pubkey, false, None); + extract_generation(contract_data) + } - self.add_ethereum_account(address, true).await; - } - Action::EvmSetCode { address, code } => { - info!("set code {address} -> {} bytes", code.len()); + async fn _add_legacy_storage( + &self, + legacy_storage: &LegacyStorageData, + info: &AccountInfo<'_>, + pubkey: Pubkey, + ) -> NeonResult<&RefCell> { + let generation = self + ._get_contract_generation_limited(legacy_storage.address) + .await?; + let storage_data = self.add_empty_account(pubkey); - self.add_ethereum_account(address, true).await; - } - Action::EvmSelfDestruct { address } => { - info!("selfdestruct {address}"); + if Some(legacy_storage.generation) == generation { + let cells = legacy_storage.read_cells(info); - self.add_ethereum_account(address, true).await; - } - Action::ExternalInstruction { - program_id, - accounts, - fee, - .. - } => { - info!("external call {program_id}"); - - self.add_solana_account(*program_id, false).await; - - for account in accounts { - self.add_solana_account(account.pubkey, account.is_writable) - .await; - } + let mut storage_data = storage_data.borrow_mut(); + self.create_ethereum_storage(&mut storage_data)?; + + storage_data.expand(StorageCell::required_account_size(cells.len())); + storage_data.lamports = self.rent.minimum_balance(storage_data.get_length()); + let mut storage = + StorageCell::from_account(&self.program_id, storage_data.into_account_info())?; + storage.cells_mut().copy_from_slice(&cells); + self.mark_legacy_account(pubkey, true, Some(storage_data.lamports)); + } else { + self.mark_legacy_account(pubkey, true, Some(0)); + } + Ok(storage_data) + } - gas = gas.saturating_add(*fee); + async fn add_account( + &self, + pubkey: Pubkey, + account: &Account, + ) -> NeonResult<&RefCell> { + let mut account = account.clone(); + let info = account_info(&pubkey, &mut account); + if *info.owner == self.program_id { + let tag = evm_loader::account::tag(&self.program_id, &info)?; + match tag { + evm_loader::account::TAG_ACCOUNT_BALANCE + | evm_loader::account::TAG_ACCOUNT_CONTRACT + | evm_loader::account::TAG_STORAGE_CELL => { + // TODO: update header from previous revisions + let account_data = AccountData::new_from_account(pubkey, &account); + self.mark_account(pubkey, false); + Ok(self + .accounts + .insert(pubkey, Box::new(RefCell::new(account_data)))) + } + evm_loader::account::legacy::TAG_ACCOUNT_CONTRACT_DEPRECATED => self + ._add_legacy_account(&info) + .map(|(contract, _balance)| contract), + evm_loader::account::legacy::TAG_STORAGE_CELL_DEPRECATED => { + let legacy_storage = LegacyStorageData::from_account(&self.program_id, &info)?; + self._add_legacy_storage(&legacy_storage, &info, pubkey) + .await + } + evm_loader::account::TAG_EMPTY => Ok(self.add_empty_account(pubkey)), + _ => { + unimplemented!(); } } + } else { + let account_data = AccountData::new_from_account(pubkey, &account); + self.mark_account(pubkey, false); + Ok(self + .accounts + .insert(pubkey, Box::new(RefCell::new(account_data)))) } + } - gas + fn add_empty_account(&self, pubkey: Pubkey) -> &RefCell { + let account_data = AccountData::new(pubkey); + self.mark_account(pubkey, false); + info!("add_empty_account(pubkey={pubkey}, account_data={account_data:?})"); + self.accounts + .insert(pubkey, Box::new(RefCell::new(account_data))) } - pub async fn apply_accounts_operations(&self, operations: AccountsOperations) -> u64 { - let mut gas = 0_u64; - let rent = Rent::get().expect("Rent get error"); + async fn use_account( + &self, + pubkey: Pubkey, + is_writable: bool, + ) -> NeonResult<&RefCell> { + info!("use_account(pubkey={pubkey}, is_writable={is_writable})"); - let mut iterations = 0_usize; + if pubkey == self.operator() { + return Err(EvmLoaderError::InvalidAccountForCall(pubkey).into()); + } - let mut accounts = self.accounts.write().await; - for (address, operation) in operations { - let new_size = match operation { - AccountOperation::Create { space } => space, - AccountOperation::Resize { to, .. } => to, - }; - accounts.entry(address).and_modify(|a| { - a.size = new_size; - a.additional_resize_steps = - new_size.saturating_sub(a.size_current).saturating_sub(1) - / MAX_PERMITTED_DATA_INCREASE; - iterations = iterations.max(a.additional_resize_steps); - }); + self.mark_account(pubkey, is_writable); - let allocate_cost = rent.minimum_balance(new_size); - gas = gas.saturating_add(allocate_cost); + if let Some(account) = self.accounts.get(&pubkey) { + return Ok(account); } - let iterations_cost = (iterations as u64) * LAMPORTS_PER_SIGNATURE; + let account = self._get_account_from_rpc(pubkey).await?; + if let Some(account) = account { + info!("found account for pubkey={pubkey} in RPC account={account:?}"); + self.add_account(pubkey, account).await + } else { + info!("account not found in RPC, adding empty account for pubkey={pubkey}"); + Ok(self.add_empty_account(pubkey)) + } + } + + async fn get_balance_account( + &self, + address: Address, + chain_id: u64, + ) -> NeonResult<&RefCell> { + let (pubkey, _) = address.find_balance_address(self.program_id(), chain_id); - gas.saturating_add(iterations_cost) + if let Some(account) = self.accounts.get(&pubkey) { + return Ok(account); + } + + match self._get_account_from_rpc(pubkey).await? { + Some(account) => self.add_account(pubkey, account).await, + None => { + if chain_id == self.default_chain_id() { + let (legacy_pubkey, _) = address.find_solana_address(self.program_id()); + if self.accounts.get(&legacy_pubkey).is_some() { + // We already have information about contract account (empty or filled with data). + // So the balance should be updated, but it is missed. So return the empty account. + Ok(self.add_empty_account(pubkey)) + } else { + // We didn't process contract account and we doesn't have any information about it. + // So we can try to process account which can be a legacy. + if let Some(legacy_account) = + self._get_account_from_rpc(legacy_pubkey).await? + { + self.add_account(legacy_pubkey, legacy_account).await?; + self.accounts + .get(&pubkey) + .map_or_else(|| Ok(self.add_empty_account(pubkey)), Ok) + } else { + self.add_empty_account(legacy_pubkey); + Ok(self.add_empty_account(pubkey)) + } + } + } else { + Ok(self.add_empty_account(pubkey)) + } + } + } } - async fn ethereum_account_map_or(&self, address: &Address, default: R, f: F) -> R + async fn get_contract_account(&self, address: Address) -> NeonResult<&RefCell> { + let (pubkey, _) = address.find_solana_address(self.program_id()); + + if let Some(account) = self.accounts.get(&pubkey) { + return Ok(account); + } + + match self._get_account_from_rpc(pubkey).await? { + Some(account) => self.add_account(pubkey, account).await, + None => Ok(self.add_empty_account(pubkey)), + } + } + + async fn get_storage_account( + &self, + address: Address, + index: U256, + ) -> NeonResult<&RefCell> { + let (base, _) = address.find_solana_address(self.program_id()); + let cell_address = StorageCellAddress::new(self.program_id(), &base, &index); + let cell_pubkey = *cell_address.pubkey(); + + if let Some(account) = self.accounts.get(&cell_pubkey) { + return Ok(account); + } + + match self._get_account_from_rpc(cell_pubkey).await? { + Some(account) => self.add_account(cell_pubkey, account).await, + None => Ok(self.add_empty_account(cell_pubkey)), + } + } + + pub async fn ethereum_balance_map_or( + &self, + address: Address, + chain_id: u64, + default: R, + action: F, + ) -> NeonResult where - F: FnOnce(&EthereumAccount) -> R, + F: FnOnce(&BalanceAccount) -> R, { - self.add_ethereum_account(address, false).await; - - let mut accounts = self.accounts.write().await; - let solana_account = accounts.get_mut(address).expect("get account error"); - - if let Some(account_data) = &mut solana_account.data { - let info = account_info(solana_account.account.as_ref(), account_data); - EthereumAccount::from_account(&self.evm_loader, &info) - .map(|mut ether_account| { - if let Some(account_overrides) = &self.state_overrides { - if let Some(account_override) = account_overrides.get(address) { - account_override.apply(&mut ether_account); - } - } - ether_account - }) - .map_or(default, |a| f(&a)) + let mut balance_data = self + .get_balance_account(address, chain_id) + .await? + .borrow_mut(); + if balance_data.is_empty() { + Ok(default) } else { - default + let account_info = balance_data.into_account_info(); + let balance = BalanceAccount::from_account(self.program_id(), account_info)?; + Ok(action(&balance)) } } - async fn ethereum_contract_map_or(&self, address: &Address, default: R, f: F) -> R + pub async fn ethereum_contract_map_or( + &self, + address: Address, + default: R, + action: F, + ) -> NeonResult where - F: FnOnce(ether_contract::ContractData) -> R, + F: FnOnce(&ContractAccount) -> R, { - self.add_ethereum_account(address, false).await; + let mut contract_data = self.get_contract_account(address).await?.borrow_mut(); + if contract_data.is_empty() { + Ok(default) + } else { + let account_info = contract_data.into_account_info(); + let contract = ContractAccount::from_account(self.program_id(), account_info)?; + Ok(action(&contract)) + } + } - let mut accounts = self.accounts.write().await; - let solana_account = accounts.get_mut(address).expect("get account error"); + pub async fn ethereum_storage_map_or( + &self, + address: Address, + index: U256, + default: R, + action: F, + ) -> NeonResult + where + F: FnOnce(&StorageCell) -> R, + { + let mut storage_data = self.get_storage_account(address, index).await?.borrow_mut(); + if storage_data.is_empty() { + Ok(default) + } else { + let account_info = storage_data.into_account_info(); + let storage = StorageCell::from_account(self.program_id(), account_info)?; + Ok(action(&storage)) + } + } - if let Some(account_data) = &mut solana_account.data { - let info = account_info(solana_account.account.as_ref(), account_data); - let account = EthereumAccount::from_account(&self.evm_loader, &info); - match &account { - Ok(a) => a.contract_data().map_or(default, f), - Err(_) => default, - } + fn create_ethereum_balance( + &'a self, + account_data: &'a mut RefMut, + address: Address, + chain_id: u64, + ) -> evm_loader::error::Result { + let required_len = BalanceAccount::required_account_size(); + account_data.assign(self.program_id)?; + account_data.expand(required_len); + account_data.lamports = self.rent.minimum_balance(account_data.get_length()); + + BalanceAccount::initialize( + account_data.into_account_info(), + &self.program_id, + address, + chain_id, + ) + } + + fn get_or_create_ethereum_balance( + &'a self, + account_data: &'a mut RefMut, + address: Address, + chain_id: u64, + ) -> evm_loader::error::Result { + if account_data.is_empty() { + self.create_ethereum_balance(account_data, address, chain_id) } else { - default + BalanceAccount::from_account(&self.program_id, account_data.into_account_info()) } } -} -impl<'a> AccountStorage for EmulatorAccountStorage<'a> { - fn neon_token_mint(&self) -> &Pubkey { - info!("neon_token_mint"); - &self.neon_token_mint + fn create_ethereum_contract( + &'a self, + account_data: &'a mut RefMut, + address: Address, + chain_id: u64, + generation: u32, + code: &[u8], + ) -> evm_loader::error::Result { + self.mark_account(account_data.pubkey, true); + let required_len = ContractAccount::required_account_size(code); + account_data.assign(self.program_id)?; + account_data.expand(required_len); + account_data.lamports = self.rent.minimum_balance(account_data.get_length()); + + ContractAccount::initialize( + account_data.into_account_info(), + &self.program_id, + address, + chain_id, + generation, + code, + ) + } + + fn create_ethereum_storage( + &'a self, + account_data: &'a mut RefMut, + ) -> evm_loader::error::Result { + self.mark_account(account_data.pubkey, true); + account_data.assign(self.program_id)?; + account_data.expand(StorageCell::required_account_size(0)); + account_data.lamports = self.rent.minimum_balance(account_data.get_length()); + + StorageCell::initialize(account_data.into_account_info(), &self.program_id) } - fn operator(&self) -> &Pubkey { - info!("operator"); - &FAKE_OPERATOR + fn get_or_create_ethereum_storage( + &'a self, + account_data: &'a mut RefMut, + ) -> evm_loader::error::Result { + if account_data.is_empty() { + self.create_ethereum_storage(account_data) + } else { + StorageCell::from_account(&self.program_id, account_data.into_account_info()) + } + } + + async fn mint( + &mut self, + address: Address, + chain_id: u64, + value: U256, + ) -> evm_loader::error::Result<()> { + info!("mint {address}:{chain_id} {value}"); + let mut balance_data = self + .get_balance_account(address, chain_id) + .await + .map_err(map_neon_error)? + .borrow_mut(); + + let mut balance = + self.get_or_create_ethereum_balance(&mut balance_data, address, chain_id)?; + balance.mint(value)?; + balance.update_lamports(&self.rent); + self.mark_account(balance_data.pubkey, true); + + Ok(()) + } + + pub fn used_accounts(&self) -> Vec { + self.used_accounts + .clone() + .into_map() + .values() + .map(|v| v.borrow().clone()) + .collect::>() + } + + pub fn accounts_get(&self, pubkey: &Pubkey) -> Option> { + self.accounts.get(pubkey).map(RefCell::borrow) + } + + pub fn get_upgrade_rent(&self) -> evm_loader::error::Result { + let mut lamports_collected = 0u64; + let mut lamports_spend = 0u64; + for (_, used_account) in self.used_accounts.clone().into_tuple_vec() { + let used_account = used_account.borrow(); + let pubkey = used_account.pubkey; + if let Some(lamports_after_upgrade) = used_account.lamports_after_upgrade { + let orig_lamports = self + .accounts_cache + .get(&used_account.pubkey) + .unwrap_or(&None) + .as_ref() + .map_or(0, |v| v.lamports); + debug!("Upgrade rent: {pubkey} {orig_lamports} -> {lamports_after_upgrade}"); + if lamports_after_upgrade > orig_lamports { + lamports_spend += lamports_after_upgrade - orig_lamports; + } else { + lamports_collected += orig_lamports - lamports_after_upgrade; + } + } + } + debug!( + "Upgrade rent: {lamports_spend} - {lamports_collected} = {}", + lamports_spend.saturating_sub(lamports_collected) + ); + Ok(lamports_spend.saturating_sub(lamports_collected)) } + pub fn get_regular_rent(&self) -> evm_loader::error::Result { + let accounts = self.accounts.clone(); + let mut old_lamports_sum = 0u64; + let mut new_lamports_sum = 0u64; + for (pubkey, account) in &accounts.into_map() { + if *pubkey == system_program::ID { + continue; + } + + let (original_lamports, original_size) = + self.accounts_cache.get(pubkey).map_or((0, 0), |v| { + v.as_ref().map_or((0, 0), |v| (v.lamports, v.data.len())) + }); + + let lamports_after_upgrade = self + .used_accounts + .get(pubkey) + .and_then(|v| v.borrow().lamports_after_upgrade); + + let new_acc = account.borrow(); + let new_lamports = new_acc.lamports; + let new_size = new_acc.get_length(); + + if new_acc.is_busy() && new_lamports < self.rent.minimum_balance(new_acc.get_length()) { + info!("Account {pubkey} is not rent exempt"); + return Err(ProgramError::AccountNotRentExempt.into()); + } + + let old_lamports = lamports_after_upgrade.unwrap_or(original_lamports); + old_lamports_sum += old_lamports; + new_lamports_sum += new_lamports; + + #[allow(clippy::cast_possible_wrap)] + let diff = new_lamports as i64 - old_lamports as i64; + debug!("Changes in rent: {pubkey} {old_lamports} -> {new_lamports} = {diff} | {original_size} -> {new_size} {lamports_after_upgrade:?}"); + } + let changes_in_rent = new_lamports_sum.saturating_sub(old_lamports_sum); + info!("Changes in rent: {changes_in_rent} = {new_lamports_sum} - {old_lamports_sum}"); + Ok(changes_in_rent) + } + + pub fn get_changes_in_rent(&self) -> evm_loader::error::Result { + Ok(self.get_upgrade_rent()? + self.get_regular_rent()?) + } + + pub fn is_timestamp_used(&self) -> bool { + *self.timestamp_used.borrow() + } + + pub fn logs(&self) -> Vec { + self.logs.clone() + } +} + +impl LogCollector for EmulatorAccountStorage<'_, T> { + fn collect_log( + &mut self, + address: &[u8; 20], + topics: [[u8; 32]; N], + data: &[u8], + ) { + self.logs.push(Log { + address: address.into(), + topics: topics.iter().map(Into::into).collect(), + data: data.into(), + block_hash: None, + block_number: None, + transaction_hash: None, + transaction_index: None, + log_index: None, + transaction_log_index: None, + log_type: None, + removed: None, + }); + } +} + +#[async_trait(?Send)] +impl AccountStorage for EmulatorAccountStorage<'_, T> { fn program_id(&self) -> &Pubkey { debug!("program_id"); - &self.evm_loader + &self.program_id + } + + fn operator(&self) -> Pubkey { + info!("operator"); + self.operator } fn block_number(&self) -> U256 { @@ -511,215 +971,517 @@ impl<'a> AccountStorage for EmulatorAccountStorage<'a> { fn block_timestamp(&self) -> U256 { info!("block_timestamp"); + *self.timestamp_used.borrow_mut() = true; self.block_timestamp.try_into().unwrap() } - fn block_hash(&self, slot: u64) -> [u8; 32] { - info!("block_hash {slot}"); + fn rent(&self) -> &Rent { + &self.rent + } - block(self.add_solana_account(slot_hashes::ID, false)); + fn return_data(&self) -> Option<(Pubkey, Vec)> { + info!("return_data"); + self.return_data + .as_ref() + .map(|data| (data.program_id, data.data.clone())) + } - if let Ok(Some(slot_hashes_account)) = block(self.get_account(&slot_hashes::ID)) { - let slot_hashes_data = slot_hashes_account.data.as_slice(); - find_slot_hash(slot, slot_hashes_data) - } else { - panic!("Error querying account {} from Solana", slot_hashes::ID) + fn set_return_data(&mut self, data: &[u8]) { + info!("set_return_data"); + self.return_data = Some(TransactionReturnData { + program_id: self.program_id, + data: data.to_vec(), + }); + } + + async fn block_hash(&self, slot: u64) -> [u8; 32] { + info!("block_hash {slot}"); + + if let Ok(account) = self.use_account(slot_hashes::ID, false).await { + let account_data = account.borrow(); + let data = account_data.data(); + if !data.is_empty() { + return find_slot_hash(slot, data); + } } + panic!("Error querying account {} from Solana", slot_hashes::ID) } - fn exists(&self, address: &Address) -> bool { - info!("exists {address}"); + async fn nonce(&self, address: Address, chain_id: u64) -> u64 { + info!("nonce {address} {chain_id}"); - block(self.add_ethereum_account(address, false)); + self.ethereum_balance_map_or( + address, + chain_id, + u64::default(), + |account: &BalanceAccount| account.nonce(), + ) + .await + .unwrap() + } - let accounts = block(self.accounts.read()); - accounts.contains_key(address) + async fn balance(&self, address: Address, chain_id: u64) -> U256 { + info!("balance {address} {chain_id}"); + + self.ethereum_balance_map_or( + address, + chain_id, + U256::default(), + |account: &BalanceAccount| account.balance(), + ) + .await + .unwrap() } - fn nonce(&self, address: &Address) -> u64 { - info!("nonce {address}"); + fn is_valid_chain_id(&self, chain_id: u64) -> bool { + for chain in &self.chains { + if chain.id == chain_id { + return true; + } + } - block(self.ethereum_account_map_or(address, 0_u64, |a| a.trx_count)) + false } - fn balance(&self, address: &Address) -> U256 { - info!("balance {address}"); + fn chain_id_to_token(&self, chain_id: u64) -> Pubkey { + for chain in &self.chains { + if chain.id == chain_id { + return chain.token; + } + } - block(self.ethereum_account_map_or(address, U256::ZERO, |a| a.balance)) + unreachable!(); } - fn code_size(&self, address: &Address) -> usize { - info!("code_size {address}"); + fn default_chain_id(&self) -> u64 { + for chain in &self.chains { + if chain.name == "neon" { + return chain.id; + } + } - block(self.ethereum_account_map_or(address, 0, |a| a.code_size as usize)) + unreachable!(); } - fn code_hash(&self, address: &Address) -> [u8; 32] { - use solana_sdk::keccak::hash; + async fn contract_chain_id(&self, address: Address) -> evm_loader::error::Result { + let default_value = Err(EvmLoaderError::Custom(std::format!( + "Account {address} - invalid tag" + ))); - info!("code_hash {address}"); + self.ethereum_contract_map_or(address, default_value, |a| Ok(a.chain_id())) + .await + .unwrap() + } + + fn contract_pubkey(&self, address: Address) -> (Pubkey, u8) { + address.find_solana_address(self.program_id()) + } - // https://eips.ethereum.org/EIPS/eip-1052 - // https://eips.ethereum.org/EIPS/eip-161 - let is_non_existent_account = block(self.ethereum_account_map_or(address, true, |a| { - a.trx_count == 0 && a.balance == 0 && a.code_size == 0 - })); + fn balance_pubkey(&self, address: Address, chain_id: u64) -> (Pubkey, u8) { + address.find_balance_address(self.program_id(), chain_id) + } - if is_non_existent_account { - return <[u8; 32]>::default(); + fn storage_cell_pubkey(&self, address: Address, index: U256) -> Pubkey { + let base = self.contract_pubkey(address).0; + if index < U256::from(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u64) { + base + } else { + let address = StorageCellAddress::new(self.program_id(), &base, &index); + *address.pubkey() } + } - // return empty hash(&[]) as a default value, or code's hash if contract exists - block( - self.ethereum_contract_map_or(address, hash(&[]).to_bytes(), |c| { - hash(&c.code()).to_bytes() - }), - ) + async fn code_size(&self, address: Address) -> usize { + info!("code_size {address}"); + + self.code(address).await.len() } - fn code(&self, address: &Address) -> evm_loader::evm::Buffer { + async fn code(&self, address: Address) -> evm_loader::evm::Buffer { use evm_loader::evm::Buffer; info!("code {address}"); - block( - self.ethereum_contract_map_or(address, Buffer::empty(), |c| { - self.state_overrides - .as_ref() - .and_then(|account_overrides| account_overrides.get(address)?.code.as_ref()) - .map_or_else( - || Buffer::from_slice(&c.code()), - |code| Buffer::from_slice(&code.0), - ) - }), - ) + // TODO: move to reading data from Solana node + // let code_override = self.account_override(address, |a| a.code.clone()); + // if let Some(code_override) = code_override { + // return Buffer::from_vec(code_override.0); + // } + + let code = self + .ethereum_contract_map_or(address, Vec::default(), |c| c.code().to_vec()) + .await + .unwrap(); + + Buffer::from_vector(code.into_vector()) } - fn generation(&self, address: &Address) -> u32 { - let value = block(self.ethereum_account_map_or(address, 0_u32, |c| c.generation)); + async fn storage(&self, address: Address, index: U256) -> [u8; 32] { + // TODO: move to reading data from Solana node + // let storage_override = self.account_override(address, |a| a.storage(index)); + // if let Some(storage_override) = storage_override { + // return storage_override; + // } + + let value = if index < U256::from(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u64) { + let index: usize = index.as_usize(); + self.ethereum_contract_map_or(address, [0_u8; 32], |c| c.storage_value(index)) + .await + .unwrap() + } else { + let subindex = (index & 0xFF).as_u8(); + let index = index & !U256::new(0xFF); + + self.ethereum_storage_map_or(address, index, <[u8; 32]>::default(), |cell| { + cell.get(subindex) + }) + .await + .unwrap() + }; + + info!("storage {address} -> {index} = {}", hex::encode(value)); - info!("account generation {address} - {value}"); value } - fn storage(&self, address: &Address, index: &U256) -> [u8; 32] { - if let Some(account_overrides) = &self.state_overrides { - if let Some(account_override) = account_overrides.get(address) { - match (&account_override.state, &account_override.state_diff) { - (None, None) => (), - (Some(_), Some(_)) => { - panic!("Account {address} has both `state` and `stateDiff` overrides") - } - (Some(state), None) => { - return state - .get(index) - .map(|value| value.to_be_bytes()) - .unwrap_or_default() - } - (None, Some(state_diff)) => { - if let Some(value) = state_diff.get(index) { - return value.to_be_bytes(); - } - } + async fn clone_solana_account(&self, address: &Pubkey) -> OwnedAccountInfo { + info!("clone_solana_account {}", address); + + if *address == self.operator() { + let mut account = fake_operator(); + let info = account_info(address, &mut account); + OwnedAccountInfo::from_account_info(self.program_id(), &info) + } else { + let account = self + .use_account(*address, false) + .await + .expect("Error querying account from Solana"); + + let mut account_data = account.borrow_mut(); + let info = account_data.into_account_info(); + OwnedAccountInfo::from_account_info(self.program_id(), &info) + } + } + + async fn map_solana_account(&self, address: &Pubkey, action: F) -> R + where + F: FnOnce(&AccountInfo) -> R, + { + let account = self + .use_account(*address, false) + .await + .expect("Error querying account from Solana"); + + let mut account_data = account.borrow_mut(); + let info = account_data.into_account_info(); + action(&info) + } +} + +#[allow(clippy::needless_pass_by_value)] +fn map_neon_error(e: NeonError) -> EvmLoaderError { + EvmLoaderError::Custom(e.to_string()) +} + +#[async_trait(?Send)] +impl SyncedAccountStorage for EmulatorAccountStorage<'_, T> { + async fn set_code( + &mut self, + address: Address, + chain_id: u64, + code: Vector, + ) -> evm_loader::error::Result<()> { + info!("set_code {address} -> {} bytes", code.len()); + { + let mut account_data = self + .get_contract_account(address) + .await + .map_err(map_neon_error)? + .borrow_mut(); + let pubkey = account_data.pubkey; + + if account_data.is_empty() { + self.create_ethereum_contract(&mut account_data, address, chain_id, 0, &code)?; + } else { + let contract = ContractAccount::from_account( + self.program_id(), + account_data.into_account_info(), + )?; + if contract.code().len() > 0 { + return Err(EvmLoaderError::AccountAlreadyInitialized( + account_data.pubkey, + )); + } + let new_account_data = RefCell::new(AccountData::new(pubkey)); + { + let mut new_account = new_account_data.borrow_mut(); + let mut new_contract = self.create_ethereum_contract( + &mut new_account, + address, + chain_id, + contract.generation(), + &code, + )?; + let storage = *contract.storage(); + new_contract.set_storage_multiple_values(0, &storage); } + *account_data = new_account_data.replace_with(|_| AccountData::new(pubkey)); } } - let value = if *index < U256::from(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT) { - let index: usize = index.as_usize() * 32; - block( - self.ethereum_contract_map_or(address, <[u8; 32]>::default(), |c| { - c.storage()[index..index + 32].try_into().unwrap() - }), - ) + + let realloc = ContractAccount::required_account_size(&code) + / solana_sdk::entrypoint::MAX_PERMITTED_DATA_INCREASE; + self.realloc_iterations = self.realloc_iterations.max(realloc as u64); + + Ok(()) + } + + async fn set_storage( + &mut self, + address: Address, + index: U256, + value: [u8; 32], + ) -> evm_loader::error::Result<()> { + info!("set_storage {address} -> {index} = {}", hex::encode(value)); + const STATIC_STORAGE_LIMIT: U256 = U256::new(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u128); + + if index < STATIC_STORAGE_LIMIT { + let mut contract_data = self + .get_contract_account(address) + .await + .map_err(map_neon_error)? + .borrow_mut(); + + let mut contract = if contract_data.is_empty() { + self.create_ethereum_contract(&mut contract_data, address, 0, 0, &[])? + } else { + ContractAccount::from_account(self.program_id(), contract_data.into_account_info())? + }; + contract.set_storage_value(index.as_usize(), &value); + contract.update_lamports(&self.rent); + self.mark_account(contract_data.pubkey, true); } else { let subindex = (index & 0xFF).as_u8(); let index = index & !U256::new(0xFF); - let (base, _) = address.find_solana_address(self.program_id()); - let storage_address = EthereumStorageAddress::new(self.program_id(), &base, &index); + let mut storage_data = self + .get_storage_account(address, index) + .await + .map_err(map_neon_error)? + .borrow_mut(); + + let mut storage = self.get_or_create_ethereum_storage(&mut storage_data)?; + storage.update(subindex, &value)?; + storage.update_lamports(&self.rent); + self.mark_account(storage_data.pubkey, true); + } - block(self.add_solana_account(*storage_address.pubkey(), false)); + Ok(()) + } - let rpc_response = block(self.get_account(storage_address.pubkey())) - .expect("Error querying account from Solana"); + async fn increment_nonce( + &mut self, + address: Address, + chain_id: u64, + ) -> evm_loader::error::Result<()> { + info!("nonce increment {address} {chain_id}"); + let mut balance_data = self + .get_balance_account(address, chain_id) + .await + .map_err(map_neon_error)? + .borrow_mut(); + let mut balance = + self.get_or_create_ethereum_balance(&mut balance_data, address, chain_id)?; + balance.increment_nonce()?; + balance.update_lamports(&self.rent); + self.mark_account(balance_data.pubkey, true); + + Ok(()) + } - if let Some(mut account) = rpc_response { - if solana_sdk::system_program::check_id(&account.owner) { - debug!("read storage system owned"); - <[u8; 32]>::default() - } else { - let account_info = account_info(storage_address.pubkey(), &mut account); - let storage = EthereumStorage::from_account(&self.evm_loader, &account_info) - .expect("EthereumAccount ctor error"); - if (storage.address != *address) - || (storage.index != index) - || (storage.generation != self.generation(address)) - { - debug!("storage collision"); - <[u8; 32]>::default() - } else { - storage.get(subindex) - } + async fn transfer( + &mut self, + from_address: Address, + to_address: Address, + chain_id: u64, + value: U256, + ) -> evm_loader::error::Result<()> { + self.burn(from_address, chain_id, value).await?; + self.mint(to_address, chain_id, value).await?; + + Ok(()) + } + + async fn burn( + &mut self, + address: Address, + chain_id: u64, + value: U256, + ) -> evm_loader::error::Result<()> { + info!("burn {address} {chain_id} {value}"); + let mut balance_data = self + .get_balance_account(address, chain_id) + .await + .map_err(map_neon_error)? + .borrow_mut(); + self.mark_account(balance_data.pubkey, true); + + let mut balance = + self.get_or_create_ethereum_balance(&mut balance_data, address, chain_id)?; + balance.burn(value)?; + balance.update_lamports(&self.rent); + + Ok(()) + } + + async fn execute_external_instruction( + &mut self, + instruction: Instruction, + seeds: Vector>>, + _fee: u64, + emulated_internally: bool, + ) -> evm_loader::error::Result<()> { + use solana_sdk::{message::Message, signature::Signer, transaction::Transaction}; + + info!("execute_external_instruction: {instruction:?}"); + info!("Operator: {}", self.operator); + self.execute_status.external_solana_call |= !emulated_internally; + + let mut solana_simulator = SolanaSimulator::new(self) + .await + .map_err(|e| EvmLoaderError::Custom(e.to_string()))?; + + solana_simulator.set_clock(Clock { + slot: self.block_number, + epoch_start_timestamp: self.block_timestamp, + epoch: 0, + leader_schedule_epoch: 0, + unix_timestamp: self.block_timestamp, + }); + + let signers = seeds + .iter() + .map(|s| { + let seed = s.iter().map(Vector::as_slice).collect::>(); + let signer = Pubkey::create_program_address(&seed, &self.program_id)?; + Ok(signer) + }) + .collect::, PubkeyError>>()?; + info!("Signers: {signers:?}"); + + let mut accounts = Vec::new(); + accounts.push(instruction.program_id); + self.mark_account(instruction.program_id, false); + + for meta in &instruction.accounts { + if meta.pubkey != self.operator { + self.use_account(meta.pubkey, meta.is_writable) + .await + .map_err(map_neon_error)?; + if meta.is_signer && !signers.contains(&meta.pubkey) { + return Err(ProgramError::MissingRequiredSignature.into()); } - } else { - debug!("storage account doesn't exist"); - <[u8; 32]>::default() } - }; + accounts.push(meta.pubkey); + } - info!("storage {address} -> {index} = {}", hex::encode(value)); + solana_simulator + .sync_accounts(self, &accounts) + .await + .map_err(|e| EvmLoaderError::Custom(e.to_string()))?; + + let trx = Transaction::new_unsigned(Message::new( + &[instruction.clone()], + Some(&solana_simulator.payer().pubkey()), + )); + + let result = solana_simulator + .simulate_legacy_transaction(trx) + .map_err(|e| EvmLoaderError::Custom(e.to_string()))?; + + if let Err(error) = result.result { + return Err(EvmLoaderError::ExternalCallFailed( + instruction.program_id, + error.to_string(), + )); + } - value - } + if let Some(return_data) = result.return_data { + self.return_data = Some(return_data); + } - fn solana_account_space(&self, address: &Address) -> Option { - block(self.ethereum_account_map_or(address, None, |account| Some(account.info.data_len()))) - } + for meta in &instruction.accounts { + if meta.pubkey == self.operator { + continue; + } + let account = result + .post_simulation_accounts + .iter() + .find(|(pubkey, _)| *pubkey == meta.pubkey) + .map(|(_, account)| account) + .ok_or_else(|| { + EvmLoaderError::Custom(format!("Account {} not found", meta.pubkey)) + })?; + + let mut account_data = self + .accounts + .get(&meta.pubkey) + .ok_or_else(|| { + EvmLoaderError::Custom(format!("Account data {} not found", meta.pubkey)) + })? + .borrow_mut(); + + *account_data = AccountData::new_from_account(meta.pubkey, account); + } - fn chain_id(&self) -> u64 { - info!("chain_id"); + Ok(()) + } - self.chain_id + fn snapshot(&mut self) { + info!("snapshot"); + self.call_stack.push(self.accounts.clone()); + self.logs_stack.push(self.logs.len()); } - fn clone_solana_account(&self, address: &Pubkey) -> OwnedAccountInfo { - info!("clone_solana_account {}", address); + fn revert_snapshot(&mut self) { + info!("revert_snapshot"); + self.accounts = self.call_stack.pop().expect("No snapshots to revert"); - if address == &FAKE_OPERATOR { - OwnedAccountInfo { - key: FAKE_OPERATOR, - is_signer: true, - is_writable: false, - lamports: 100 * 1_000_000_000, - data: vec![], - owner: solana_sdk::system_program::ID, - executable: false, - rent_epoch: 0, - } + if self.execute_status.external_solana_call { + self.execute_status.reverts_after_solana_calls = true; } else { - block(self.add_solana_account(*address, false)); + self.execute_status.reverts_before_solana_calls = true; + } - let mut account = block(self.get_account(address)) - .unwrap_or_default() - .unwrap_or_default(); - let info = account_info(address, &mut account); + let logs_stack_len = self + .logs_stack + .pop() + .expect("Fatal Error: Inconsistent EVM Logs Stack"); - OwnedAccountInfo::from_account_info(self.program_id(), &info) + self.logs.truncate(logs_stack_len); + + if self.logs_stack.is_empty() { + // sanity check + assert!(self.logs.is_empty()); } } - fn map_solana_account(&self, address: &Pubkey, action: F) -> R - where - F: FnOnce(&AccountInfo) -> R, - { - block(self.add_solana_account(*address, false)); - - let mut account = block(self.get_account(address)) - .unwrap_or_default() - .unwrap_or_default(); - let info = account_info(address, &mut account); + fn commit_snapshot(&mut self) { + self.call_stack.pop().expect("No snapshots to commit"); + self.logs_stack + .pop() + .expect("Fatal Error: Inconsistent EVM Logs Stack"); + } +} - action(&info) +#[must_use] +pub const fn fake_operator() -> Account { + Account { + lamports: 100 * 1_000_000_000, + data: vec![], + owner: system_program::ID, + executable: false, + rent_epoch: 0, } } @@ -737,9 +1499,6 @@ pub fn account_info<'a>(key: &'a Pubkey, account: &'a mut Account) -> AccountInf } } -pub fn make_solana_program_address(ether_address: &Address, program_id: &Pubkey) -> (Pubkey, u8) { - Pubkey::find_program_address( - &[&[ACCOUNT_SEED_VERSION], ether_address.as_bytes()], - program_id, - ) -} +#[cfg(test)] +#[path = "./account_storage_tests.rs"] +mod account_storage_tests; diff --git a/evm_loader/lib/src/account_storage_tests.rs b/evm_loader/lib/src/account_storage_tests.rs new file mode 100644 index 000000000..d5f68dc50 --- /dev/null +++ b/evm_loader/lib/src/account_storage_tests.rs @@ -0,0 +1,1792 @@ +use super::*; +use crate::rpc; +use crate::tracing::AccountOverride; +use evm_loader::types::vector::VectorVecExt; +use hex_literal::hex; +use std::collections::HashMap; +use std::str::FromStr; + +const STORAGE_LENGTH: usize = 32 * STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT; + +mod mock_rpc_client { + use crate::commands::get_config::BuildConfigSimulator; + use crate::NeonResult; + use crate::{commands::get_config::ConfigSimulator, rpc::Rpc}; + use async_trait::async_trait; + use solana_client::client_error::Result as ClientResult; + use solana_sdk::account::Account; + use solana_sdk::clock::{Slot, UnixTimestamp}; + use solana_sdk::pubkey::Pubkey; + use std::collections::HashMap; + + pub struct MockRpcClient { + accounts: HashMap, + } + + impl MockRpcClient { + pub fn new(accounts: &[(Pubkey, Account)]) -> Self { + Self { + accounts: accounts.iter().cloned().collect(), + } + } + } + + #[async_trait(?Send)] + impl Rpc for MockRpcClient { + async fn get_account(&self, key: &Pubkey) -> ClientResult> { + let result = self.accounts.get(key).cloned(); + Ok(result) + } + + async fn get_multiple_accounts( + &self, + pubkeys: &[Pubkey], + ) -> ClientResult>> { + let result = pubkeys + .iter() + .map(|key| self.accounts.get(key).cloned()) + .collect::>(); + Ok(result) + } + + async fn get_block_time(&self, _slot: Slot) -> ClientResult { + Ok(UnixTimestamp::default()) + } + + async fn get_slot(&self) -> ClientResult { + Ok(Slot::default()) + } + + async fn get_deactivated_solana_features(&self) -> ClientResult> { + Ok(vec![]) + } + } + + #[async_trait(?Send)] + impl BuildConfigSimulator for MockRpcClient { + fn use_cache(&self) -> bool { + false + } + async fn build_config_simulator(&self, _program_id: Pubkey) -> NeonResult { + unimplemented!(); + } + } +} + +async fn get_overriden_nonce_and_balance( + address: Address, + tx_chain_id: u64, + nonce_chain_id: u64, + overrides: Option, +) -> (u64, U256) { + let mut fixture = Fixture::new(); + fixture.state_overrides = overrides; + let storage = fixture + .build_account_storage_with_chain_id(Some(tx_chain_id)) + .await; + + ( + storage.nonce(address, nonce_chain_id).await, + storage.balance(address, nonce_chain_id).await, + ) +} + +async fn get_balance_account_info( + storage: &EmulatorAccountStorage<'_, T>, + action: F, +) -> NeonResult +where + F: FnOnce(&BalanceAccount) -> R, +{ + let mut balance_data = storage + .get_balance_account(ACTUAL_BALANCE.address, LEGACY_CHAIN_ID) + .await? + .borrow_mut(); + let balance_account = + BalanceAccount::from_account(&storage.program_id, balance_data.into_account_info()); + + Ok(action(&balance_account?)) +} + +#[allow(clippy::too_many_arguments)] +fn create_legacy_ether_contract( + program_id: &Pubkey, + rent: &Rent, + address: Address, + balance: U256, + trx_count: u64, + generation: u32, + code: &[u8], + storage: &[[u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT], +) -> Account { + let data_length = if (!code.is_empty()) || (generation > 0) { + 1 + LegacyEtherData::SIZE + 32 * STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT + code.len() + } else { + 1 + LegacyEtherData::SIZE + }; + let mut data = vec![0u8; data_length]; + + let data_ref = arrayref::array_mut_ref![data, 0, 1 + LegacyEtherData::SIZE]; + let ( + tag_ptr, + address_ptr, + bump_seed_ptr, + trx_count_ptr, + balance_ptr, + generation_ptr, + code_size_ptr, + rw_blocked_ptr, + ) = arrayref::mut_array_refs![data_ref, 1, 20, 1, 8, 32, 4, 4, 1]; + + *tag_ptr = LegacyEtherData::TAG.to_le_bytes(); + *address_ptr = *address.as_bytes(); + *bump_seed_ptr = 0u8.to_le_bytes(); + *trx_count_ptr = trx_count.to_le_bytes(); + *balance_ptr = balance.to_le_bytes(); + *generation_ptr = generation.to_le_bytes(); + *code_size_ptr = u32::try_from(code.len()) + .expect("Expected code value") + .to_le_bytes(); + *rw_blocked_ptr = 0u8.to_le_bytes(); + + if (generation > 0) || (!code.is_empty()) { + let storage_offset = 1 + LegacyEtherData::SIZE; + + let storage_ptr = &mut data[storage_offset..][..STORAGE_LENGTH]; + let storage_source = unsafe { + let ptr: *const u8 = storage.as_ptr().cast(); + std::slice::from_raw_parts(ptr, 32 * STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT) + }; + storage_ptr.copy_from_slice(storage_source); + + let code_offset = storage_offset + STORAGE_LENGTH; + let code_ptr = &mut data[code_offset..][..code.len()]; + code_ptr.copy_from_slice(code); + } + + Account { + lamports: rent.minimum_balance(data.len()), + data, + owner: *program_id, + executable: false, + rent_epoch: 0, + } +} + +fn create_legacy_ether_account( + program_id: &Pubkey, + rent: &Rent, + address: Address, + balance: U256, + trx_count: u64, +) -> Account { + let storage = [[0u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT]; + create_legacy_ether_contract( + program_id, + rent, + address, + balance, + trx_count, + 0u32, + &[], + &storage, + ) +} + +struct ActualStorage { + index: U256, + values: &'static [(u8, [u8; 32])], +} + +struct LegacyStorage { + generation: u32, + index: U256, + values: &'static [(u8, [u8; 32])], +} + +impl ActualStorage { + pub fn account_with_pubkey( + &self, + program_id: &Pubkey, + rent: &Rent, + address: Address, + ) -> (Pubkey, Account) { + let (contract, _) = address.find_solana_address(program_id); + let cell_address = StorageCellAddress::new(program_id, &contract, &self.index); + let cell_pubkey = *cell_address.pubkey(); + let mut account_data = AccountData::new(cell_pubkey); + account_data.assign(*program_id).unwrap(); + account_data.expand(StorageCell::required_account_size(self.values.len())); + account_data.lamports = rent.minimum_balance(account_data.get_length()); + let mut storage = + StorageCell::initialize(account_data.into_account_info(), program_id).unwrap(); + for (cell, (index, value)) in storage.cells_mut().iter_mut().zip(self.values.iter()) { + cell.subindex = *index; + cell.value.copy_from_slice(value); + } + ( + cell_pubkey, + Account { + lamports: rent.minimum_balance(account_data.get_length()), + data: account_data.data().to_vec(), + owner: *program_id, + executable: false, + rent_epoch: 0, + }, + ) + } +} + +impl LegacyStorage { + pub const fn required_account_size(count: usize) -> usize { + 1 + LegacyStorageData::SIZE + std::mem::size_of::<(u8, [u8; 32])>() * count + } + pub fn account_with_pubkey( + &self, + program_id: &Pubkey, + rent: &Rent, + address: Address, + ) -> (Pubkey, Account) { + let (contract, _) = address.find_solana_address(program_id); + let cell_address = StorageCellAddress::new(program_id, &contract, &self.index); + let cell_pubkey = *cell_address.pubkey(); + let mut data = vec![0u8; Self::required_account_size(self.values.len())]; + + let data_ref = arrayref::array_mut_ref![data, 0, 1 + LegacyStorageData::SIZE]; + let (tag_ptr, address_ptr, generation_ptr, index_ptr) = + arrayref::mut_array_refs![data_ref, 1, 20, 4, 32]; + + *tag_ptr = LegacyStorageData::TAG.to_le_bytes(); + *address_ptr = *address.as_bytes(); + *generation_ptr = self.generation.to_le_bytes(); + *index_ptr = self.index.to_le_bytes(); + + let storage = unsafe { + let data = &mut data[1 + LegacyStorageData::SIZE..]; + let ptr = data.as_mut_ptr().cast::<(u8, [u8; 32])>(); + std::slice::from_raw_parts_mut(ptr, self.values.len()) + }; + storage.copy_from_slice(self.values); + + let account = Account { + lamports: rent.minimum_balance(data.len()), + data, + owner: *program_id, + executable: false, + rent_epoch: 0, + }; + + (cell_pubkey, account) + } +} + +struct LegacyAccount { + pub address: Address, + pub balance: U256, + pub nonce: u64, +} + +impl LegacyAccount { + pub fn account_with_pubkey(&self, program_id: &Pubkey, rent: &Rent) -> (Pubkey, Account) { + ( + self.address.find_solana_address(program_id).0, + create_legacy_ether_account(program_id, rent, self.address, self.balance, self.nonce), + ) + } +} +struct LegacyContract { + pub address: Address, + pub balance: U256, + pub nonce: u64, + pub generation: u32, + pub code: &'static [u8], + pub storage: [[u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT], + + pub legacy_storage: LegacyStorage, + pub outdate_storage: LegacyStorage, +} + +impl LegacyContract { + fn account_with_pubkey(&self, program_id: &Pubkey, rent: &Rent) -> (Pubkey, Account) { + ( + self.address.find_solana_address(program_id).0, + create_legacy_ether_contract( + program_id, + rent, + self.address, + self.balance, + self.nonce, + self.generation, + self.code, + &self.storage, + ), + ) + } + + pub fn legacy_storage_with_pubkey( + &self, + program_id: &Pubkey, + rent: &Rent, + ) -> (Pubkey, Account) { + self.legacy_storage + .account_with_pubkey(program_id, rent, self.address) + } + + pub fn outdate_storage_with_pubkey( + &self, + program_id: &Pubkey, + rent: &Rent, + ) -> (Pubkey, Account) { + self.outdate_storage + .account_with_pubkey(program_id, rent, self.address) + } +} + +struct ActualBalance { + pub address: Address, + pub chain_id: u64, + pub balance: U256, + pub nonce: u64, +} + +impl ActualBalance { + pub fn account_with_pubkey(&self, program_id: &Pubkey, rent: &Rent) -> (Pubkey, Account) { + let (pubkey, _) = self.address.find_balance_address(program_id, self.chain_id); + let mut account_data = AccountData::new(pubkey); + account_data.assign(*program_id).unwrap(); + account_data.expand(BalanceAccount::required_account_size()); + account_data.lamports = rent.minimum_balance(account_data.get_length()); + + let mut balance = BalanceAccount::initialize( + account_data.into_account_info(), + program_id, + self.address, + self.chain_id, + ) + .unwrap(); + balance.mint(self.balance).unwrap(); + balance.increment_nonce_by(self.nonce).unwrap(); + + ( + pubkey, + Account { + lamports: rent.minimum_balance(account_data.get_length()), + data: account_data.data().to_vec(), + owner: *program_id, + executable: false, + rent_epoch: 0, + }, + ) + } +} + +struct ActualContract { + pub address: Address, + pub chain_id: u64, + pub generation: u32, + pub code: &'static [u8], + pub storage: [[u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT], + + pub actual_storage: ActualStorage, + pub legacy_storage: LegacyStorage, + pub outdate_storage: LegacyStorage, +} + +impl ActualContract { + pub fn account_with_pubkey(&self, program_id: &Pubkey, rent: &Rent) -> (Pubkey, Account) { + let (pubkey, _) = self.address.find_solana_address(program_id); + let mut account_data = AccountData::new(pubkey); + account_data.assign(*program_id).unwrap(); + account_data.expand(ContractAccount::required_account_size(self.code)); + account_data.lamports = rent.minimum_balance(account_data.get_length()); + + let mut contract = ContractAccount::initialize( + account_data.into_account_info(), + program_id, + self.address, + self.chain_id, + self.generation, + self.code, + ) + .unwrap(); + contract.set_storage_multiple_values(0, &self.storage); + + ( + pubkey, + Account { + lamports: rent.minimum_balance(account_data.get_length()), + data: account_data.data().to_vec(), + owner: *program_id, + executable: false, + rent_epoch: 0, + }, + ) + } + + pub fn actual_storage_with_pubkey( + &self, + program_id: &Pubkey, + rent: &Rent, + ) -> (Pubkey, Account) { + self.actual_storage + .account_with_pubkey(program_id, rent, self.address) + } + + pub fn legacy_storage_with_pubkey( + &self, + program_id: &Pubkey, + rent: &Rent, + ) -> (Pubkey, Account) { + self.legacy_storage + .account_with_pubkey(program_id, rent, self.address) + } + + pub fn outdate_storage_with_pubkey( + &self, + program_id: &Pubkey, + rent: &Rent, + ) -> (Pubkey, Account) { + self.outdate_storage + .account_with_pubkey(program_id, rent, self.address) + } +} + +const LEGACY_CHAIN_ID: u64 = 1; +const EXTRA_CHAIN_ID: u64 = 2; +const MISSING_ADDRESS: Address = Address(hex!("7a250d5630b4cf539739df2c5dacb4c659f24800")); + +const MISSING_STORAGE_INDEX: U256 = U256::new(256u128); +const ACTUAL_STORAGE_INDEX: U256 = U256::new(2 * 256u128); +const LEGACY_STORAGE_INDEX: U256 = U256::new(3 * 256u128); +const OUTDATE_STORAGE_INDEX: U256 = U256::new(4 * 256u128); + +const ACTUAL_BALANCE: ActualBalance = ActualBalance { + address: Address(hex!("7a250d5630b4cf539739df2c5dacb4c659f24810")), + chain_id: LEGACY_CHAIN_ID, + balance: U256::new(1513), + nonce: 41, +}; + +const ACTUAL_BALANCE2: ActualBalance = ActualBalance { + address: Address(hex!("7a250d5630b4cf539739df2c5dacb4c659f24811")), + chain_id: EXTRA_CHAIN_ID, + balance: U256::new(5134), + nonce: 14, +}; + +const ACTUAL_CONTRACT: ActualContract = ActualContract { + address: Address(hex!("7a250d5630b4cf539739df2c5dacb4c659f24c11")), + chain_id: LEGACY_CHAIN_ID, + generation: 4, + code: &[0x03, 0x04, 0x05], + storage: [[14u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT], + actual_storage: ActualStorage { + index: ACTUAL_STORAGE_INDEX, + values: &[(0u8, [64u8; 32])], + }, + legacy_storage: LegacyStorage { + generation: 4, + index: LEGACY_STORAGE_INDEX, + values: &[(0u8, [54u8; 32])], + }, + outdate_storage: LegacyStorage { + generation: 3, + index: OUTDATE_STORAGE_INDEX, + values: &[(0u8, [34u8; 32])], + }, +}; + +const ACTUAL_SUICIDE: ActualContract = ActualContract { + address: Address(hex!("7a250d5630b4cf539739df2c5dacb4c659f24d10")), + chain_id: LEGACY_CHAIN_ID, + generation: 12, + code: &[], + storage: [[0u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT], // It's matter that suicide contract doesn't contains any values in storage! + actual_storage: ActualStorage { + index: U256::ZERO, + values: &[], + }, + legacy_storage: LegacyStorage { + generation: 0, + index: U256::ZERO, + values: &[], + }, + outdate_storage: LegacyStorage { + generation: 11, + index: LEGACY_STORAGE_INDEX, + values: &[(0u8, [13u8; 32])], + }, +}; + +const LEGACY_ACCOUNT: LegacyAccount = LegacyAccount { + address: Address(hex!("7a250d5630b4cf539739df2c5dacb4c659f24820")), + balance: U256::new(10234), + nonce: 123, +}; + +const LEGACY_CONTRACT: LegacyContract = LegacyContract { + address: Address(hex!("7a250d5630b4cf539739df2c5dacb4c659f24c21")), + balance: U256::new(6153), + nonce: 1, + generation: 3, + code: &[0x01, 0x02, 0x03], + storage: [[0u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT], + + legacy_storage: LegacyStorage { + generation: 3, + index: LEGACY_STORAGE_INDEX, + values: &[(0u8, [23u8; 32])], + }, + outdate_storage: LegacyStorage { + generation: 2, + index: OUTDATE_STORAGE_INDEX, + values: &[(0u8, [43u8; 32])], + }, +}; + +const LEGACY_CONTRACT_NO_BALANCE: LegacyContract = LegacyContract { + address: Address(hex!("7a250d5630b4cf539739df2c5dacb4c659f24c20")), + balance: U256::ZERO, + nonce: 0, + generation: 2, + code: &[0x01, 0x02, 0x03, 0x04], + storage: [[53u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT], + legacy_storage: LegacyStorage { + generation: 0, + index: U256::ZERO, + values: &[], + }, + outdate_storage: LegacyStorage { + generation: 1, + index: U256::ZERO, + values: &[], + }, +}; + +const LEGACY_SUICIDE: LegacyContract = LegacyContract { + address: Address(hex!("7a250d5630b4cf539739df2c5dacb4c659f24d21")), + balance: U256::new(41234), + nonce: 413, + generation: 5, + code: &[], + storage: [[42u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT], + + legacy_storage: LegacyStorage { + generation: 413, + index: LEGACY_STORAGE_INDEX, + values: &[(0u8, [65u8; 32])], + }, + outdate_storage: LegacyStorage { + generation: 412, + index: OUTDATE_STORAGE_INDEX, + values: &[(0u8, [76u8; 32])], + }, +}; + +struct Fixture { + program_id: Pubkey, + chains: Vec, + rent: Rent, + mock_rpc: mock_rpc_client::MockRpcClient, + block_overrides: Option, + state_overrides: Option>, + solana_overrides: Option, +} + +impl Fixture { + pub fn new() -> Self { + let rent = Rent::default(); + let program_id = Pubkey::from_str("53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io").unwrap(); + let accounts = vec![ + ( + Pubkey::from_str("SysvarRent111111111111111111111111111111111").unwrap(), + Account { + lamports: 1_009_200, + data: bincode::serialize(&rent).unwrap(), + owner: Pubkey::from_str("Sysvar1111111111111111111111111111111111111").unwrap(), + executable: false, + rent_epoch: 0, + }, + ), + ACTUAL_BALANCE.account_with_pubkey(&program_id, &rent), + ACTUAL_BALANCE2.account_with_pubkey(&program_id, &rent), + LEGACY_ACCOUNT.account_with_pubkey(&program_id, &rent), + ACTUAL_CONTRACT.account_with_pubkey(&program_id, &rent), + ACTUAL_CONTRACT.actual_storage_with_pubkey(&program_id, &rent), + ACTUAL_CONTRACT.legacy_storage_with_pubkey(&program_id, &rent), + ACTUAL_CONTRACT.outdate_storage_with_pubkey(&program_id, &rent), + ACTUAL_SUICIDE.account_with_pubkey(&program_id, &rent), + ACTUAL_SUICIDE.outdate_storage_with_pubkey(&program_id, &rent), + LEGACY_CONTRACT.account_with_pubkey(&program_id, &rent), + LEGACY_CONTRACT.legacy_storage_with_pubkey(&program_id, &rent), + LEGACY_CONTRACT.outdate_storage_with_pubkey(&program_id, &rent), + LEGACY_CONTRACT_NO_BALANCE.account_with_pubkey(&program_id, &rent), + LEGACY_SUICIDE.account_with_pubkey(&program_id, &rent), + LEGACY_SUICIDE.outdate_storage_with_pubkey(&program_id, &rent), + ]; + + let rpc_client = mock_rpc_client::MockRpcClient::new(&accounts); + + Self { + program_id, + chains: vec![ + ChainInfo { + id: LEGACY_CHAIN_ID, + name: "neon".to_string(), + token: Pubkey::new_unique(), + }, + ChainInfo { + id: EXTRA_CHAIN_ID, + name: "usdt".to_string(), + token: Pubkey::new_unique(), + }, + ], + rent, + mock_rpc: rpc_client, + block_overrides: None, + state_overrides: None, + solana_overrides: None, + } + } + + pub async fn build_account_storage_with_chain_id( + &self, + tx_chain_id: Option, + ) -> EmulatorAccountStorage<'_, mock_rpc_client::MockRpcClient> { + EmulatorAccountStorage::new( + &self.mock_rpc, + self.program_id, + Some(self.chains.clone()), + self.block_overrides.clone(), + self.state_overrides.clone(), + self.solana_overrides.clone(), + tx_chain_id, + ) + .await + .unwrap() + } + + pub async fn build_account_storage( + &self, + ) -> EmulatorAccountStorage<'_, mock_rpc_client::MockRpcClient> { + EmulatorAccountStorage::new( + &self.mock_rpc, + self.program_id, + Some(self.chains.clone()), + self.block_overrides.clone(), + self.state_overrides.clone(), + self.solana_overrides.clone(), + None, + ) + .await + .unwrap() + } + + pub fn balance_pubkey(&self, address: Address, chain_id: u64) -> Pubkey { + address.find_balance_address(&self.program_id, chain_id).0 + } + + pub fn legacy_pubkey(&self, address: Address) -> Pubkey { + address.find_solana_address(&self.program_id).0 + } + + pub fn contract_pubkey(&self, address: Address) -> Pubkey { + address.find_solana_address(&self.program_id).0 + } + + pub fn storage_pubkey(&self, address: Address, index: U256) -> Pubkey { + if index < U256::new(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u128) { + self.contract_pubkey(address) + } else { + let index = index & !U256::new(0xFF); + let base = self.contract_pubkey(address); + let cell_address = StorageCellAddress::new(&self.program_id, &base, &index); + *cell_address.pubkey() + } + } + + pub fn storage_rent(&self, count: usize) -> u64 { + self.rent + .minimum_balance(StorageCell::required_account_size(count)) + } + + pub fn legacy_storage_rent(&self, count: usize) -> u64 { + self.rent + .minimum_balance(LegacyStorage::required_account_size(count)) + } + + pub fn balance_rent(&self) -> u64 { + self.rent + .minimum_balance(BalanceAccount::required_account_size()) + } + + pub fn legacy_rent(&self, code_len: Option) -> u64 { + let data_length = code_len.map_or(1 + LegacyEtherData::SIZE, |len| { + 1 + LegacyEtherData::SIZE + 32 * STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT + len + }); + self.rent.minimum_balance(data_length) + } + + pub fn contract_rent(&self, code: &[u8]) -> u64 { + self.rent + .minimum_balance(ContractAccount::required_account_size(code)) + } +} + +impl<'rpc, T: Rpc> EmulatorAccountStorage<'rpc, T> { + pub fn verify_used_accounts(&self, expected: &[(Pubkey, bool, bool)]) { + let mut expected = expected.to_vec(); + expected.sort_by_key(|(k, _, _)| *k); + let mut actual = self + .used_accounts() + .iter() + .map(|v| (v.pubkey, v.is_writable, v.is_legacy)) + .collect::>(); + actual.sort_by_key(|(k, _, _)| *k); + assert_eq!(actual, expected); + } + + pub fn verify_upgrade_rent(&self, added_rent: u64, removed_rent: u64) { + assert_eq!( + self.get_upgrade_rent().unwrap(), + added_rent.saturating_sub(removed_rent) + ); + } + + pub fn verify_regular_rent(&self, added_rent: u64, removed_rent: u64) { + assert_eq!( + self.get_regular_rent().unwrap(), + added_rent.saturating_sub(removed_rent) + ); + } +} + +#[tokio::test] +async fn test_read_balance_missing_account() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + assert_eq!( + storage.balance(MISSING_ADDRESS, LEGACY_CHAIN_ID).await, + U256::ZERO + ); + assert_eq!(storage.nonce(MISSING_ADDRESS, LEGACY_CHAIN_ID).await, 0); + + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(MISSING_ADDRESS, LEGACY_CHAIN_ID), + false, + false, + ), + (fixture.legacy_pubkey(MISSING_ADDRESS), false, false), + ]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_balance_missing_account_extra_chain() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + assert_eq!( + storage.balance(MISSING_ADDRESS, EXTRA_CHAIN_ID).await, + U256::ZERO + ); + assert_eq!(storage.nonce(MISSING_ADDRESS, EXTRA_CHAIN_ID).await, 0); + + storage.verify_used_accounts(&[( + fixture.balance_pubkey(MISSING_ADDRESS, EXTRA_CHAIN_ID), + false, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_balance_actual_account() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let acc = &ACTUAL_BALANCE; + assert_eq!( + storage.balance(acc.address, acc.chain_id).await, + acc.balance + ); + assert_eq!(storage.nonce(acc.address, acc.chain_id).await, acc.nonce); + + storage.verify_used_accounts(&[( + fixture.balance_pubkey(acc.address, acc.chain_id), + false, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_balance_actual_account_extra_chain() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let acc = &ACTUAL_BALANCE2; + assert_eq!(acc.chain_id, EXTRA_CHAIN_ID); + assert_eq!( + storage.balance(acc.address, acc.chain_id).await, + acc.balance + ); + assert_eq!(storage.nonce(acc.address, acc.chain_id).await, acc.nonce); + + storage.verify_used_accounts(&[( + fixture.balance_pubkey(acc.address, acc.chain_id), + false, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_balance_legacy_account() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let acc = &LEGACY_ACCOUNT; + assert_eq!( + storage.balance(acc.address, LEGACY_CHAIN_ID).await, + acc.balance + ); + assert_eq!(storage.nonce(acc.address, LEGACY_CHAIN_ID).await, acc.nonce); + + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(acc.address, LEGACY_CHAIN_ID), + true, + true, + ), + (fixture.legacy_pubkey(acc.address), true, true), + ]); + storage.verify_upgrade_rent(fixture.balance_rent(), fixture.legacy_rent(None)); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_modify_actual_and_missing_account() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let from = &ACTUAL_BALANCE; + let amount = U256::new(10); + assert_eq!(from.chain_id, LEGACY_CHAIN_ID); + assert!(storage + .transfer(from.address, MISSING_ADDRESS, from.chain_id, amount) + .await + .is_ok()); + + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(from.address, from.chain_id), + true, + false, + ), + ( + fixture.balance_pubkey(MISSING_ADDRESS, LEGACY_CHAIN_ID), + true, + false, + ), + (fixture.legacy_pubkey(MISSING_ADDRESS), false, false), + ]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(fixture.balance_rent(), 0); + + assert_eq!( + storage.balance(from.address, from.chain_id).await, + from.balance - amount + ); + assert_eq!( + storage.balance(MISSING_ADDRESS, LEGACY_CHAIN_ID).await, + amount + ); +} + +#[tokio::test] +async fn test_modify_actual_and_missing_account_extra_chain() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let from = &ACTUAL_BALANCE2; + let amount = U256::new(11); + assert_eq!(from.chain_id, EXTRA_CHAIN_ID); + assert!(storage + .transfer(from.address, MISSING_ADDRESS, from.chain_id, amount) + .await + .is_ok()); + + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(from.address, from.chain_id), + true, + false, + ), + ( + fixture.balance_pubkey(MISSING_ADDRESS, from.chain_id), + true, + false, + ), + ]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(fixture.balance_rent(), 0); + + assert_eq!( + storage.balance(from.address, from.chain_id).await, + from.balance - amount + ); + assert_eq!( + storage.balance(MISSING_ADDRESS, from.chain_id).await, + amount + ); +} + +#[tokio::test] +async fn test_modify_actual_and_legacy_account() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let from = &ACTUAL_BALANCE; + let to = &LEGACY_ACCOUNT; + let amount = U256::new(10); + assert_eq!(from.chain_id, LEGACY_CHAIN_ID); + assert!(storage + .transfer(from.address, to.address, from.chain_id, amount) + .await + .is_ok()); + + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(from.address, from.chain_id), + true, + false, + ), + ( + fixture.balance_pubkey(to.address, LEGACY_CHAIN_ID), + true, + true, + ), + (fixture.legacy_pubkey(to.address), true, true), + ]); + storage.verify_upgrade_rent(fixture.balance_rent(), fixture.legacy_rent(None)); + storage.verify_regular_rent(0, 0); + + assert_eq!( + storage.balance(from.address, from.chain_id).await, + from.balance - amount + ); + assert_eq!( + storage.balance(to.address, LEGACY_CHAIN_ID).await, + to.balance + amount + ); +} + +#[tokio::test] +async fn test_read_missing_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + assert_eq!(*storage.code(MISSING_ADDRESS).await, [0u8; 0]); + assert_eq!( + storage.storage(MISSING_ADDRESS, U256::ZERO).await, + [0u8; 32] + ); + storage.verify_used_accounts(&[(fixture.contract_pubkey(MISSING_ADDRESS), false, false)]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); + + assert_eq!( + storage + .storage( + MISSING_ADDRESS, + U256::new(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u128) + ) + .await, + [0u8; 32] + ); +} + +#[tokio::test] +async fn test_read_legacy_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + assert_eq!( + *storage.code(LEGACY_CONTRACT.address).await, + *LEGACY_CONTRACT.code + ); + assert_eq!( + storage.storage(LEGACY_CONTRACT.address, U256::ZERO).await, + [0u8; 32] + ); + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(LEGACY_CONTRACT.address, LEGACY_CHAIN_ID), + true, + true, + ), + (fixture.contract_pubkey(LEGACY_CONTRACT.address), true, true), + ]); + storage.verify_upgrade_rent( + fixture.balance_rent() + fixture.contract_rent(LEGACY_CONTRACT.code), + fixture.legacy_rent(Some(LEGACY_CONTRACT.code.len())), + ); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_legacy_contract_no_balance() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &LEGACY_CONTRACT_NO_BALANCE; + assert_eq!(*storage.code(contract.address).await, *contract.code); + assert_eq!( + storage.storage(contract.address, U256::ZERO).await, + [53u8; 32] + ); + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(contract.address, LEGACY_CHAIN_ID), + false, + true, + ), + (fixture.contract_pubkey(contract.address), true, true), + ]); + storage.verify_upgrade_rent( + fixture.contract_rent(contract.code), + fixture.legacy_rent(Some(contract.code.len())), + ); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_actual_suicide_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &ACTUAL_SUICIDE; + assert_eq!(*storage.code(contract.address).await, [0u8; 0]); + assert_eq!( + storage.storage(contract.address, U256::ZERO).await, + [0u8; 32] + ); + storage.verify_used_accounts(&[(fixture.contract_pubkey(contract.address), false, false)]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_legacy_suicide_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &LEGACY_SUICIDE; + assert_eq!(*storage.code(contract.address).await, [0u8; 0]); + assert_eq!( + storage.storage(contract.address, U256::ZERO).await, + [0u8; 32] + ); + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(contract.address, LEGACY_CHAIN_ID), + true, + true, + ), + (fixture.contract_pubkey(contract.address), true, true), + ]); + storage.verify_upgrade_rent( + fixture.balance_rent() + fixture.contract_rent(contract.code), + fixture.legacy_rent(Some(contract.code.len())), + ); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_deploy_at_missing_contract() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let code = hex!("14643165").to_vec(); + assert!(storage + .set_code(MISSING_ADDRESS, LEGACY_CHAIN_ID, code.clone().into_vector()) + .await + .is_ok()); + storage.verify_used_accounts(&[(fixture.contract_pubkey(MISSING_ADDRESS), true, false)]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(fixture.contract_rent(&code), 0); +} + +#[tokio::test] +async fn test_deploy_at_actual_balance() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let code = hex!("14643165").to_vec(); + let acc = &ACTUAL_BALANCE; + assert!(storage + .set_code(acc.address, LEGACY_CHAIN_ID, code.clone().into_vector()) + .await + .is_ok()); + storage.verify_used_accounts(&[(fixture.contract_pubkey(acc.address), true, false)]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(fixture.contract_rent(&code), 0); +} + +#[tokio::test] +async fn test_deploy_at_actual_contract() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let code = hex!("62345987").to_vec(); + let contract = &ACTUAL_CONTRACT; + assert_eq!( + storage + .set_code(contract.address, LEGACY_CHAIN_ID, code.into_vector()) + .await + .unwrap_err() + .to_string(), + EvmLoaderError::AccountAlreadyInitialized(fixture.contract_pubkey(contract.address)) + .to_string() + ); + storage.verify_used_accounts(&[(fixture.contract_pubkey(contract.address), false, false)]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_deploy_at_legacy_account() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let code = hex!("37455846").to_vec(); + let contract = &LEGACY_ACCOUNT; + assert!(storage + .set_code( + contract.address, + LEGACY_CHAIN_ID, + code.clone().into_vector() + ) + .await + .is_ok()); + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(contract.address, LEGACY_CHAIN_ID), + true, + true, + ), + (fixture.contract_pubkey(contract.address), true, true), + ]); + storage.verify_upgrade_rent(fixture.balance_rent(), fixture.legacy_rent(None)); + storage.verify_regular_rent(fixture.contract_rent(&code), 0); +} + +#[tokio::test] +async fn test_deploy_at_legacy_contract() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let code = hex!("13412971").to_vec(); + let contract = &LEGACY_CONTRACT; + assert_eq!( + storage + .set_code(contract.address, LEGACY_CHAIN_ID, code.into_vector()) + .await + .unwrap_err() + .to_string(), + EvmLoaderError::AccountAlreadyInitialized(fixture.contract_pubkey(contract.address)) + .to_string() + ); + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(contract.address, LEGACY_CHAIN_ID), + true, + true, + ), + (fixture.contract_pubkey(contract.address), true, true), + ]); + storage.verify_upgrade_rent( + fixture.balance_rent() + fixture.contract_rent(contract.code), + fixture.legacy_rent(Some(contract.code.len())), + ); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_deploy_at_actual_suicide() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let code = hex!("13412971").to_vec(); + let contract = &ACTUAL_SUICIDE; + // TODO: Should we deploy new contract by the previous address? + assert!(storage + .set_code( + contract.address, + LEGACY_CHAIN_ID, + code.clone().into_vector() + ) + .await + .is_ok(),); + storage.verify_used_accounts(&[(fixture.contract_pubkey(contract.address), true, false)]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent( + fixture.contract_rent(&code), + fixture.contract_rent(contract.code), + ); +} + +#[tokio::test] +async fn test_deploy_at_legacy_suicide() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let code = hex!("13412971").to_vec(); + let contract = &LEGACY_SUICIDE; + // TODO: Should we deploy new contract by the previous address? + assert!(storage + .set_code( + contract.address, + LEGACY_CHAIN_ID, + code.clone().into_vector() + ) + .await + .is_ok(),); + storage.verify_used_accounts(&[ + ( + fixture.balance_pubkey(contract.address, LEGACY_CHAIN_ID), + true, + true, + ), + (fixture.contract_pubkey(contract.address), true, true), + ]); + storage.verify_upgrade_rent( + fixture.balance_rent() + fixture.contract_rent(contract.code), + fixture.legacy_rent(Some(contract.code.len())), + ); + storage.verify_regular_rent( + fixture.contract_rent(&code), + fixture.contract_rent(contract.code), + ); +} + +#[tokio::test] +async fn test_read_missing_storage_for_missing_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + assert_eq!( + storage + .storage(MISSING_ADDRESS, MISSING_STORAGE_INDEX) + .await, + [0u8; 32] + ); + storage.verify_used_accounts(&[( + fixture.storage_pubkey(MISSING_ADDRESS, MISSING_STORAGE_INDEX), + false, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_missing_storage_for_actual_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &ACTUAL_CONTRACT; + assert_eq!( + storage + .storage(contract.address, MISSING_STORAGE_INDEX) + .await, + [0u8; 32] + ); + storage.verify_used_accounts(&[( + fixture.storage_pubkey(contract.address, MISSING_STORAGE_INDEX), + false, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_actual_storage_for_actual_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &ACTUAL_CONTRACT; + assert_eq!( + storage + .storage(contract.address, ACTUAL_STORAGE_INDEX) + .await, + contract.actual_storage.values[0].1 + ); + storage.verify_used_accounts(&[( + fixture.storage_pubkey(contract.address, ACTUAL_STORAGE_INDEX), + false, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_modify_new_storage_for_actual_contract() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let contract = &ACTUAL_CONTRACT; + assert_eq!( + storage + .storage(contract.address, ACTUAL_STORAGE_INDEX + 1) + .await, + [0u8; 32] + ); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); + + let new_value = [0x01u8; 32]; + assert!(storage + .set_storage(contract.address, ACTUAL_STORAGE_INDEX + 1, new_value) + .await + .is_ok()); + assert_eq!( + storage + .storage(contract.address, ACTUAL_STORAGE_INDEX + 1) + .await, + new_value + ); + storage.verify_used_accounts(&[( + fixture.storage_pubkey(contract.address, ACTUAL_STORAGE_INDEX), + true, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(fixture.storage_rent(2), fixture.storage_rent(1)); +} + +#[tokio::test] +async fn test_modify_missing_storage_for_actual_contract() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let contract = &ACTUAL_CONTRACT; + let new_value = [0x02u8; 32]; + assert!(storage + .set_storage(contract.address, MISSING_STORAGE_INDEX, new_value) + .await + .is_ok()); + assert_eq!( + storage + .storage(contract.address, MISSING_STORAGE_INDEX) + .await, + new_value + ); + storage.verify_used_accounts(&[( + fixture.storage_pubkey(contract.address, MISSING_STORAGE_INDEX), + true, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(fixture.storage_rent(1), 0); +} + +#[tokio::test] +async fn test_modify_internal_storage_for_actual_contract() { + let fixture = Fixture::new(); + let mut storage = fixture.build_account_storage().await; + + let contract = &ACTUAL_CONTRACT; + let new_value = [0x03u8; 32]; + let index = U256::new(0); + assert!(storage + .set_storage(contract.address, index, new_value) + .await + .is_ok()); + assert_eq!(storage.storage(contract.address, index).await, new_value); + storage.verify_used_accounts(&[(fixture.contract_pubkey(contract.address), true, false)]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_legacy_storage_for_actual_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &ACTUAL_CONTRACT; + assert_eq!( + storage + .storage(contract.address, LEGACY_STORAGE_INDEX) + .await, + contract.legacy_storage.values[0].1 + ); + storage.verify_used_accounts(&[ + (fixture.contract_pubkey(contract.address), false, true), + ( + fixture.storage_pubkey(contract.address, LEGACY_STORAGE_INDEX), + true, + true, + ), + ]); + storage.verify_upgrade_rent(fixture.storage_rent(1), fixture.legacy_storage_rent(1)); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_outdate_storage_for_actual_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &ACTUAL_CONTRACT; + assert_eq!( + storage + .storage(contract.address, OUTDATE_STORAGE_INDEX) + .await, + [0u8; 32] + ); + storage.verify_used_accounts(&[ + (fixture.contract_pubkey(contract.address), false, true), + ( + fixture.storage_pubkey(contract.address, OUTDATE_STORAGE_INDEX), + true, + true, + ), + ]); + storage.verify_upgrade_rent(0, fixture.legacy_storage_rent(1)); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_missing_storage_for_legacy_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &LEGACY_CONTRACT; + assert_eq!( + storage + .storage(contract.address, MISSING_STORAGE_INDEX) + .await, + [0u8; 32] + ); + storage.verify_used_accounts(&[( + fixture.storage_pubkey(contract.address, MISSING_STORAGE_INDEX), + false, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_legacy_storage_for_legacy_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &LEGACY_CONTRACT; + assert_eq!( + storage + .storage(contract.address, LEGACY_STORAGE_INDEX) + .await, + contract.legacy_storage.values[0].1 + ); + storage.verify_used_accounts(&[ + (fixture.contract_pubkey(contract.address), true, true), + ( + fixture.balance_pubkey(contract.address, LEGACY_CHAIN_ID), + true, + true, + ), + ( + fixture.storage_pubkey(contract.address, LEGACY_STORAGE_INDEX), + true, + true, + ), + ]); + storage.verify_upgrade_rent( + fixture.balance_rent() + fixture.contract_rent(contract.code) + fixture.storage_rent(1), + fixture.legacy_storage_rent(1) + fixture.legacy_rent(Some(contract.code.len())), + ); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_outdate_storage_for_legacy_contract() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &LEGACY_CONTRACT; + assert_eq!( + storage + .storage(contract.address, OUTDATE_STORAGE_INDEX) + .await, + [0u8; 32] + ); + storage.verify_used_accounts(&[ + (fixture.contract_pubkey(contract.address), true, true), + ( + fixture.balance_pubkey(contract.address, LEGACY_CHAIN_ID), + true, + true, + ), + ( + fixture.storage_pubkey(contract.address, OUTDATE_STORAGE_INDEX), + true, + true, + ), + ]); + storage.verify_upgrade_rent( + fixture.balance_rent() + fixture.contract_rent(contract.code), + fixture.legacy_storage_rent(1) + fixture.legacy_rent(Some(contract.code.len())), + ); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_missing_storage_for_legacy_suicide() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &LEGACY_SUICIDE; + assert_eq!( + storage + .storage(contract.address, MISSING_STORAGE_INDEX) + .await, + [0u8; 32] + ); + storage.verify_used_accounts(&[( + fixture.storage_pubkey(contract.address, MISSING_STORAGE_INDEX), + false, + false, + )]); + storage.verify_upgrade_rent(0, 0); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_read_outdate_storage_for_legacy_suicide() { + let fixture = Fixture::new(); + let storage = fixture.build_account_storage().await; + + let contract = &LEGACY_SUICIDE; + assert_eq!( + storage + .storage(contract.address, OUTDATE_STORAGE_INDEX) + .await, + [0u8; 32] + ); + storage.verify_used_accounts(&[ + (fixture.contract_pubkey(contract.address), true, true), + ( + fixture.balance_pubkey(contract.address, LEGACY_CHAIN_ID), + true, + true, + ), + ( + fixture.storage_pubkey(contract.address, OUTDATE_STORAGE_INDEX), + true, + true, + ), + ]); + storage.verify_upgrade_rent( + fixture.balance_rent() + fixture.contract_rent(contract.code), + fixture.legacy_storage_rent(1) + fixture.legacy_rent(Some(contract.code.len())), + ); + storage.verify_regular_rent(0, 0); +} + +#[tokio::test] +async fn test_state_overrides_nonce_and_balance() { + let expected_nonce = 17; + let expected_balance = U256::MAX; + + let overriden_state = AccountOverrides::from([ + ( + ACTUAL_BALANCE.address, + AccountOverride { + nonce: Some(expected_nonce), + balance: Some(expected_balance), + ..Default::default() + }, + ), + ( + ACTUAL_BALANCE2.address, + AccountOverride { + nonce: Some(expected_nonce), + ..Default::default() + }, + ), + ]); + + // Checking override for another acount and chain where we expect only + // nonce overriden. + assert_eq!( + get_overriden_nonce_and_balance( + ACTUAL_BALANCE2.address, + EXTRA_CHAIN_ID, + EXTRA_CHAIN_ID, + Some(overriden_state.clone()) + ) + .await, + (expected_nonce, ACTUAL_BALANCE2.balance) + ); + + // Checking override for another for first account for both + // balance and nonce. + assert_eq!( + get_overriden_nonce_and_balance( + ACTUAL_BALANCE.address, + LEGACY_CHAIN_ID, + LEGACY_CHAIN_ID, + Some(overriden_state.clone()) + ) + .await, + (expected_nonce, expected_balance) + ); + + // Override for different chain id. + assert_ne!(expected_nonce, ACTUAL_BALANCE.nonce); + assert_eq!( + get_overriden_nonce_and_balance( + ACTUAL_BALANCE.address, + EXTRA_CHAIN_ID, + LEGACY_CHAIN_ID, + Some(overriden_state.clone()) + ) + .await, + (ACTUAL_BALANCE.nonce, ACTUAL_BALANCE.balance) + ); + + // Do not override if all items are None. + assert_eq!( + get_overriden_nonce_and_balance( + ACTUAL_BALANCE.address, + LEGACY_CHAIN_ID, + LEGACY_CHAIN_ID, + Some(AccountOverrides::from([ + (ACTUAL_BALANCE.address, AccountOverride::default()), + (ACTUAL_BALANCE2.address, AccountOverride::default()) + ])) + ) + .await, + (ACTUAL_BALANCE.nonce, ACTUAL_BALANCE.balance) + ); +} + +#[tokio::test] +async fn test_storage_with_accounts_and_override() { + let expected_nonce = 17; + let expected_balance = U256::MAX; + + let rent = Rent::default(); + let program_id = Pubkey::from_str("53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io").unwrap(); + let account_tuple = ACTUAL_BALANCE.account_with_pubkey(&program_id, &rent); + let accounts_for_rpc = vec![ + (solana_sdk::sysvar::rent::id(), account_tuple.1.clone()), + account_tuple.clone(), + ]; + let rpc_client = mock_rpc_client::MockRpcClient::new(&accounts_for_rpc); + let accounts_for_storage: Vec = vec![account_tuple.0]; + let storage = EmulatorAccountStorage::with_accounts( + &rpc_client, + program_id, + &accounts_for_storage, + vec![ChainInfo { + id: LEGACY_CHAIN_ID, + name: "neon".to_string(), + token: Pubkey::new_unique(), + }] + .into(), + None, + Some(AccountOverrides::from([( + ACTUAL_BALANCE.address, + AccountOverride { + nonce: Some(expected_nonce), + balance: Some(expected_balance), + ..Default::default() + }, + )])), + None, + Some(LEGACY_CHAIN_ID), + ) + .await + .expect("Failed to create storage"); + assert_eq!( + get_balance_account_info(&storage, |account: &BalanceAccount| account.nonce()) + .await + .expect("Failed to read nonce"), + expected_nonce + ); + assert_eq!( + get_balance_account_info(&storage, |account: &BalanceAccount| account.balance()) + .await + .expect("Failed to read balance"), + expected_balance + ); +} + +#[tokio::test] +async fn test_storage_new_from_other_and_override() { + let expected_nonce = 17; + let expected_balance = U256::MAX; + + let rent = Rent::default(); + let program_id = Pubkey::from_str("53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io").unwrap(); + let account_tuple = ACTUAL_BALANCE.account_with_pubkey(&program_id, &rent); + let accounts_for_rpc = vec![ + (solana_sdk::sysvar::rent::id(), account_tuple.1.clone()), + account_tuple.clone(), + ]; + let rpc_client = mock_rpc_client::MockRpcClient::new(&accounts_for_rpc); + let accounts_for_storage: Vec = vec![account_tuple.0]; + let storage = EmulatorAccountStorage::with_accounts( + &rpc_client, + program_id, + &accounts_for_storage, + vec![ChainInfo { + id: LEGACY_CHAIN_ID, + name: "neon".to_string(), + token: Pubkey::new_unique(), + }] + .into(), + None, + Some(AccountOverrides::from([( + ACTUAL_BALANCE.address, + AccountOverride { + nonce: Some(expected_nonce), + balance: Some(expected_balance), + ..Default::default() + }, + )])), + None, + Some(LEGACY_CHAIN_ID), + ) + .await + .expect("Failed to create storage"); + + let other_storage = + EmulatorAccountStorage::new_from_other(&storage, 0, 0, Some(LEGACY_CHAIN_ID)) + .await + .expect("Failed to create a copy of storage"); + assert_eq!( + get_balance_account_info(&other_storage, |account: &BalanceAccount| account.nonce()) + .await + .expect("Failed to read nonce"), + expected_nonce + ); + assert_eq!( + get_balance_account_info(&other_storage, |account: &BalanceAccount| account.balance()) + .await + .expect("Failed to read balance"), + expected_balance + ); +} diff --git a/evm_loader/lib/src/build_info.rs b/evm_loader/lib/src/build_info.rs new file mode 100644 index 000000000..7007d2c47 --- /dev/null +++ b/evm_loader/lib/src/build_info.rs @@ -0,0 +1,8 @@ +use crate::build_info_common::SlimBuildInfo; + +build_info::build_info!(fn build_info); + +#[must_use] +pub fn get_build_info() -> SlimBuildInfo { + build_info().into() +} diff --git a/evm_loader/lib/src/build_info_common.rs b/evm_loader/lib/src/build_info_common.rs new file mode 100644 index 000000000..712145ca4 --- /dev/null +++ b/evm_loader/lib/src/build_info_common.rs @@ -0,0 +1,76 @@ +use build_info::chrono::{DateTime, Utc}; +use build_info::semver::Version; +use build_info::VersionControl::Git; +use build_info::{BuildInfo, OptimizationLevel}; +use serde::Serialize; +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Clone, Serialize)] +pub struct SlimBuildInfo { + timestamp: DateTime, + profile: String, + optimization_level: OptimizationLevel, + crate_info: CrateInfo, + compiler: CompilerInfo, + version_control: GitInfo, +} + +#[derive(Debug, Clone, Serialize)] +struct CrateInfo { + name: String, + version: Version, +} + +#[derive(Debug, Clone, Serialize)] +struct CompilerInfo { + version: Version, +} + +#[derive(Debug, Clone, Serialize)] +struct GitInfo { + commit_id: String, + dirty: bool, + branch: Option, + tags: Vec, +} + +impl From<&BuildInfo> for SlimBuildInfo { + fn from(build_info: &BuildInfo) -> Self { + let build_info = build_info.clone(); + + let crate_info = build_info.crate_info; + + let Git(git_info) = build_info + .version_control + .expect("Project should be built inside version control"); + + Self { + timestamp: build_info.timestamp, + profile: build_info.profile, + optimization_level: build_info.optimization_level, + crate_info: CrateInfo { + name: crate_info.name, + version: crate_info.version, + }, + compiler: CompilerInfo { + version: build_info.compiler.version, + }, + version_control: GitInfo { + commit_id: git_info.commit_id, + dirty: git_info.dirty, + branch: git_info.branch, + tags: git_info.tags, + }, + } + } +} + +impl Display for SlimBuildInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "BuildInfo={}", + serde_json::to_string(&self).expect("Serialization should not fail") + ) + } +} diff --git a/evm_loader/lib/src/commands/cancel_trx.rs b/evm_loader/lib/src/commands/cancel_trx.rs deleted file mode 100644 index 1d3c47c84..000000000 --- a/evm_loader/lib/src/commands/cancel_trx.rs +++ /dev/null @@ -1,64 +0,0 @@ -use log::info; - -use serde::{Deserialize, Serialize}; -use solana_sdk::{ - incinerator, - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - signature::Signature, - signer::Signer, -}; - -use evm_loader::account::State; - -use crate::{account_storage::account_info, commands::send_transaction, rpc::Rpc, NeonResult}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct CancelTrxReturn { - pub transaction: Signature, -} - -pub async fn execute( - rpc_client: &dyn Rpc, - signer: &dyn Signer, - evm_loader: Pubkey, - storage_account: &Pubkey, -) -> NeonResult { - let mut acc = rpc_client.get_account(storage_account).await?; - let storage_info = account_info(storage_account, &mut acc); - let storage = State::from_account(&evm_loader, &storage_info)?; - - let operator = &signer.pubkey(); - - let mut accounts_meta: Vec = vec![ - AccountMeta::new(*storage_account, false), // State account - AccountMeta::new(*operator, true), // Operator - AccountMeta::new(incinerator::id(), false), // Incinerator - ]; - - let blocked_accounts = storage.read_blocked_accounts()?; - for blocked_account_meta in blocked_accounts { - if blocked_account_meta.is_writable { - accounts_meta.push(AccountMeta::new(blocked_account_meta.key, false)); - } else { - accounts_meta.push(AccountMeta::new_readonly(blocked_account_meta.key, false)); - } - } - for meta in &accounts_meta { - info!("\t{:?}", meta); - } - - let cancel_with_nonce_instruction = Instruction::new_with_bincode( - evm_loader, - &(0x23_u8, storage.transaction_hash), - accounts_meta, - ); - - let instructions = vec![cancel_with_nonce_instruction]; - - let signature = send_transaction(rpc_client, signer, &instructions).await?; - - Ok(CancelTrxReturn { - transaction: signature, - }) -} diff --git a/evm_loader/lib/src/commands/collect_treasury.rs b/evm_loader/lib/src/commands/collect_treasury.rs index 7e2dcd0de..42a090293 100644 --- a/evm_loader/lib/src/commands/collect_treasury.rs +++ b/evm_loader/lib/src/commands/collect_treasury.rs @@ -1,12 +1,11 @@ -use crate::rpc::check_account_for_fee; +use crate::rpc::{check_account_for_fee, CloneRpcClient}; use crate::{ - commands::get_neon_elf::read_elf_parameters_from_account, errors::NeonError, Config, Context, - NeonResult, + commands::get_neon_elf::read_elf_parameters_from_account, errors::NeonError, Config, NeonResult, }; use evm_loader::account::{MainTreasury, Treasury}; use log::{info, warn}; use serde::{Deserialize, Serialize}; -use solana_client::nonblocking::rpc_client::RpcClient; +use solana_sdk::signature::Signer; use solana_sdk::{ instruction::{AccountMeta, Instruction}, message::Message, @@ -14,6 +13,7 @@ use solana_sdk::{ transaction::Transaction, }; use spl_token::instruction::sync_native; +use std::ops::Deref; #[derive(Debug, Serialize, Deserialize)] pub struct CollectTreasuryReturn { @@ -21,12 +21,15 @@ pub struct CollectTreasuryReturn { pub balance: u64, } -pub async fn execute(config: &Config, context: &Context) -> NeonResult { - let neon_params = read_elf_parameters_from_account(config, context).await?; - let signer = context.signer()?; +pub async fn execute( + config: &Config, + rpc_client: &CloneRpcClient, + signer: &dyn Signer, +) -> NeonResult { + let neon_params = read_elf_parameters_from_account(config, rpc_client).await?; let pool_count: u32 = neon_params - .get("NEON_POOL_COUNT") + .get("NEON_TREASURY_POOL_COUNT") .and_then(|value| value.parse().ok()) .ok_or(NeonError::IncorrectProgram(config.evm_loader))?; @@ -34,23 +37,15 @@ pub async fn execute(config: &Config, context: &Context) -> NeonResult() - .expect("cast to solana_client::rpc_client::RpcClient error"); - for i in 0..pool_count { let (aux_balance_address, _) = Treasury::address(&config.evm_loader, i); - if let Some(aux_balance_account) = context - .rpc_client + if let Some(aux_balance_account) = rpc_client .get_account_with_commitment(&aux_balance_address, config.commitment) .await? .value { - let minimal_balance = context - .rpc_client + let minimal_balance = rpc_client .get_minimum_balance_for_rent_exemption(aux_balance_account.data.len()) .await?; let available_lamports = aux_balance_account.lamports.saturating_sub(minimal_balance); @@ -71,15 +66,14 @@ pub async fn execute(config: &Config, context: &Context) -> NeonResult NeonResult NeonResult { - let (solana_address, nonce) = ether_address.find_solana_address(&evm_loader); - debug!("Create ethereum account {solana_address} <- {ether_address} {nonce}"); - - let create_account_v03_instruction = Instruction::new_with_bincode( - evm_loader, - &(0x28_u8, ether_address.as_bytes()), - vec![ - AccountMeta::new(signer.pubkey(), true), - AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new(solana_address, false), - ], - ); - - let instructions = vec![create_account_v03_instruction]; - - let mut finalize_message = Message::new(&instructions, Some(&signer.pubkey())); - let blockhash = rpc_client.get_latest_blockhash().await?; - finalize_message.recent_blockhash = blockhash; - - check_account_for_fee(rpc_client, &signer.pubkey(), &finalize_message).await?; - - let mut finalize_tx = Transaction::new_unsigned(finalize_message); - - finalize_tx.try_sign(&[signer], blockhash)?; - debug!("signed: {:x?}", finalize_tx); - - rpc_client - .send_and_confirm_transaction_with_spinner(&finalize_tx) - .await?; - - Ok(CreateEtherAccountReturn { - solana_address: solana_address.to_string(), - }) -} diff --git a/evm_loader/lib/src/commands/deposit.rs b/evm_loader/lib/src/commands/deposit.rs deleted file mode 100644 index ce5ed1669..000000000 --- a/evm_loader/lib/src/commands/deposit.rs +++ /dev/null @@ -1,117 +0,0 @@ -use log::debug; -use serde::{Deserialize, Serialize}; - -use crate::rpc::check_account_for_fee; -use crate::NeonResult; -use evm_loader::types::Address; -use solana_client::nonblocking::rpc_client::RpcClient; -use solana_sdk::signer::Signer; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - message::Message, - pubkey::Pubkey, - signature::Signature, - system_program, - transaction::Transaction, -}; -use spl_associated_token_account::get_associated_token_address; - -#[derive(Debug, Serialize, Deserialize)] -pub struct DepositReturn { - pub transaction: Signature, -} - -/// Executes subcommand `deposit`. -pub async fn execute( - rpc_client: &RpcClient, - evm_loader: Pubkey, - signer: &dyn Signer, - amount: u64, - ether_address: &Address, -) -> NeonResult { - let (ether_pubkey, _) = ether_address.find_solana_address(&evm_loader); - - let token_mint_id = evm_loader::config::token_mint::id(); - - let signer_token_pubkey = get_associated_token_address(&signer.pubkey(), &token_mint_id); - let evm_token_authority = Pubkey::find_program_address(&[b"Deposit"], &evm_loader).0; - let evm_pool_pubkey = get_associated_token_address(&evm_token_authority, &token_mint_id); - - let instructions = vec![ - spl_approve_instruction(signer.pubkey(), signer_token_pubkey, ether_pubkey, amount)?, - deposit_instruction( - evm_loader, - signer.pubkey(), - signer_token_pubkey, - evm_pool_pubkey, - ether_address, - ether_pubkey, - ), - ]; - - let mut finalize_message = Message::new(&instructions, Some(&signer.pubkey())); - let blockhash = rpc_client.get_latest_blockhash().await?; - finalize_message.recent_blockhash = blockhash; - - check_account_for_fee(rpc_client, &signer.pubkey(), &finalize_message).await?; - - let mut finalize_tx = Transaction::new_unsigned(finalize_message); - - finalize_tx.try_sign(&[signer], blockhash)?; - debug!("signed: {:x?}", finalize_tx); - - let signature = rpc_client - .send_and_confirm_transaction_with_spinner(&finalize_tx) - .await?; - - Ok(DepositReturn { - transaction: signature, - }) -} - -/// Returns instruction to approve transfer of NEON tokens. -fn spl_approve_instruction( - signer: Pubkey, - source_pubkey: Pubkey, - delegate_pubkey: Pubkey, - amount: u64, -) -> NeonResult { - use spl_token::instruction::TokenInstruction; - - let accounts = vec![ - AccountMeta::new(source_pubkey, false), - AccountMeta::new_readonly(delegate_pubkey, false), - AccountMeta::new_readonly(signer, true), - ]; - - let data = TokenInstruction::Approve { amount }.pack(); - - Ok(Instruction { - program_id: spl_token::id(), - accounts, - data, - }) -} - -/// Returns instruction to deposit NEON tokens. -fn deposit_instruction( - evm_loader: Pubkey, - signer: Pubkey, - source_pubkey: Pubkey, - destination_pubkey: Pubkey, - ether_address: &Address, - ether_account_pubkey: Pubkey, -) -> Instruction { - Instruction::new_with_bincode( - evm_loader, - &(0x27_u8, ether_address.as_bytes()), - vec![ - AccountMeta::new(source_pubkey, false), - AccountMeta::new(destination_pubkey, false), - AccountMeta::new(ether_account_pubkey, false), - AccountMeta::new_readonly(spl_token::id(), false), - AccountMeta::new(signer, true), - AccountMeta::new_readonly(system_program::id(), false), - ], - ) -} diff --git a/evm_loader/lib/src/commands/emulate.rs b/evm_loader/lib/src/commands/emulate.rs index a69174fc7..bec08eb1c 100644 --- a/evm_loader/lib/src/commands/emulate.rs +++ b/evm_loader/lib/src/commands/emulate.rs @@ -1,235 +1,273 @@ -use log::{debug, info}; -use std::fmt::{Display, Formatter}; - -use ethnum::U256; +use crate::account_data::AccountData; +use crate::commands::get_config::BuildConfigSimulator; +use crate::rpc::Rpc; +use crate::tracing::tracers::Tracer; +use crate::types::{AccountInfoLevel, EmulateRequest, TxParams}; +use crate::{ + account_storage::{EmulatorAccountStorage, SyncedAccountStorage}, + errors::NeonError, + NeonResult, +}; +use evm_loader::account_storage::AccountStorage; +use evm_loader::error::build_revert_message; use evm_loader::{ - account_storage::AccountStorage, config::{EVM_STEPS_MIN, PAYMENT_TO_TREASURE}, evm::{ExitStatus, Machine}, - executor::{Action, ExecutorState}, + executor::SyncedExecutorState, gasometer::LAMPORTS_PER_SIGNATURE, - types::{Address, Transaction}, }; +use log::{debug, error, info}; use serde::{Deserialize, Serialize}; +use serde_json::Value; +use serde_with::{hex::Hex, serde_as, DisplayFromStr}; +use solana_sdk::{account::Account, pubkey::Pubkey}; +use web3::types::Log; -use crate::types::block; -use crate::{ - account_storage::{EmulatorAccountStorage, NeonAccount, SolanaAccount}, - errors::NeonError, - rpc::Rpc, - syscall_stubs::Stubs, - types::{trace::TraceCallConfig, TxParams}, - NeonResult, -}; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SolanaAccount { + #[serde_as(as = "DisplayFromStr")] + pub pubkey: Pubkey, + pub is_writable: bool, + pub is_legacy: bool, +} +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct EmulationResult { - #[serde(serialize_with = "serde_hex_serialize")] - #[serde(deserialize_with = "serde_hex_deserialize")] - pub result: Vec, +pub struct EmulateResponse { pub exit_status: String, + pub external_solana_call: bool, + pub reverts_before_solana_calls: bool, + pub reverts_after_solana_calls: bool, + #[serde_as(as = "Hex")] + pub result: Vec, pub steps_executed: u64, pub used_gas: u64, - pub actions: Vec, + pub iterations: u64, + pub solana_accounts: Vec, + pub logs: Vec, + pub accounts_data: Option>, } -impl Display for EmulationResult { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{{ exit_status: {}, steps_executed: {}, used_gas: {}, actions: {}, result: {} }}", - self.exit_status, - self.steps_executed, - self.used_gas, - self.actions.len(), - hex::encode(&self.result), - ) +impl EmulateResponse { + pub fn revert( + e: &E, + backend: &SyncedExecutorState>, + ) -> Self { + let revert_message = build_revert_message(&e.to_string()); + let exit_status = ExitStatus::Revert(revert_message); + Self { + exit_status: exit_status.to_string(), + external_solana_call: false, + reverts_before_solana_calls: false, + reverts_after_solana_calls: false, + result: exit_status.into_result().unwrap_or_default(), + steps_executed: 0, + used_gas: 0, + iterations: 0, + solana_accounts: vec![], + logs: backend.backend().logs(), + accounts_data: None, + } } } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct EmulationResultWithAccounts { - pub accounts: Vec, - pub solana_accounts: Vec, - pub token_accounts: Vec, - #[serde(flatten)] - pub emulation_result: EmulationResult, -} +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + program_id: Pubkey, + emulate_request: EmulateRequest, + tracer: Option, +) -> NeonResult<(EmulateResponse, Option)> { + let block_overrides = emulate_request + .trace_config + .as_ref() + .and_then(|t| t.block_overrides.clone()); + let state_overrides = emulate_request + .trace_config + .as_ref() + .and_then(|t| t.state_overrides.clone()); -impl Display for EmulationResultWithAccounts { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.emulation_result) - } -} + let solana_overrides = emulate_request.solana_overrides.map(|overrides| { + overrides + .iter() + .map(|(pubkey, account)| (*pubkey, account.as_ref().map(Account::from))) + .collect() + }); -fn serde_hex_serialize(value: &[u8], s: S) -> Result -where - S: serde::Serializer, -{ - s.serialize_str(&hex::encode(value)) -} + let mut storage = EmulatorAccountStorage::with_accounts( + rpc, + program_id, + &emulate_request.accounts, + emulate_request.chains, + block_overrides, + state_overrides, + solana_overrides, + emulate_request.tx.chain_id, + ) + .await?; -fn serde_hex_deserialize<'de, D>(d: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - struct StringVisitor; - impl<'de> serde::de::Visitor<'de> for StringVisitor { - type Value = Vec; + let step_limit = emulate_request.step_limit.unwrap_or(100_000); - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - write!(formatter, "a hex-encoded string with even length") - } + let mut result = + emulate_trx(emulate_request.tx.clone(), &mut storage, step_limit, tracer).await?; - fn visit_str(self, s: &str) -> Result - where - E: serde::de::Error, + if storage.is_timestamp_used() { + let mut storage2 = + EmulatorAccountStorage::new_from_other(&storage, 5, 3, emulate_request.tx.chain_id) + .await?; + if let Ok(result2) = emulate_trx( + emulate_request.tx, + &mut storage2, + step_limit, + Option::::None, + ) + .await { - hex::decode(s).map_err(|_err| { - serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self) - }) + let response = &result.0; + let response2 = &result2.0; + + let mut combined_solana_accounts = response.solana_accounts.clone(); + response2.solana_accounts.iter().for_each(|v| { + if let Some(w) = combined_solana_accounts + .iter_mut() + .find(|x| x.pubkey == v.pubkey) + { + w.is_writable |= v.is_writable; + w.is_legacy |= v.is_legacy; + } else { + combined_solana_accounts.push(v.clone()); + } + }); + + result.0 = EmulateResponse { + // We get the result from the first response (as it is executed on the current time) + result: response.result.clone(), + exit_status: response.exit_status.to_string(), + external_solana_call: response.external_solana_call, + reverts_before_solana_calls: response.reverts_before_solana_calls, + reverts_after_solana_calls: response.reverts_after_solana_calls, + accounts_data: None, + + // ...and consumed resources from the both responses (because the real execution can occur in the future) + steps_executed: response.steps_executed.max(response2.steps_executed), + used_gas: response.used_gas.max(response2.used_gas), + iterations: response.iterations.max(response2.iterations), + solana_accounts: combined_solana_accounts, + logs: response.logs.clone(), + }; } } - d.deserialize_string(StringVisitor) -} + if let Some(level) = emulate_request.provide_account_info { + result.0.accounts_data = Some(provide_account_data( + &storage, + &result.0.solana_accounts, + &level, + )); + } -#[allow(clippy::too_many_arguments)] -pub async fn execute( - rpc_client: &dyn Rpc, - evm_loader: Pubkey, - tx_params: TxParams, - token_mint: Pubkey, - chain_id: u64, - step_limit: u64, - commitment: CommitmentConfig, - accounts: &[Address], - solana_accounts: &[Pubkey], - trace_call_config: TraceCallConfig, -) -> NeonResult { - let (emulation_result, storage) = emulate_transaction( - rpc_client, - evm_loader, - tx_params, - token_mint, - chain_id, - step_limit, - commitment, - accounts, - solana_accounts, - trace_call_config, - ) - .await?; - let accounts = block(storage.accounts.read()).values().cloned().collect(); - let solana_accounts = block(storage.solana_accounts.read()) - .values() - .cloned() - .collect(); - - Ok(EmulationResultWithAccounts { - accounts, - solana_accounts, - token_accounts: vec![], - emulation_result, - }) + Ok(result) } -#[allow(clippy::too_many_arguments)] -pub(crate) async fn emulate_transaction<'a>( - rpc_client: &'a dyn Rpc, - evm_loader: Pubkey, +async fn emulate_trx( tx_params: TxParams, - token_mint: Pubkey, - chain_id: u64, + storage: &mut EmulatorAccountStorage<'_, impl Rpc>, step_limit: u64, - commitment: CommitmentConfig, - accounts: &[Address], - solana_accounts: &[Pubkey], - trace_call_config: TraceCallConfig, -) -> Result<(EmulationResult, EmulatorAccountStorage<'a>), NeonError> { - setup_syscall_stubs(rpc_client).await?; - - let storage = EmulatorAccountStorage::with_accounts( - rpc_client, - evm_loader, - token_mint, - chain_id, - commitment, - accounts, - solana_accounts, - &trace_call_config.block_overrides, - trace_call_config.state_overrides, - ) - .await?; + tracer: Option, +) -> NeonResult<(EmulateResponse, Option)> { + info!("tx_params: {:?}", tx_params); - emulate_trx(tx_params, &storage, chain_id, step_limit) - .await - .map(move |result| (result, storage)) -} + let (origin, tx) = tx_params.into_transaction(storage).await; -pub(crate) async fn emulate_trx<'a>( - tx_params: TxParams, - storage: &'a EmulatorAccountStorage<'a>, - chain_id: u64, - step_limit: u64, -) -> Result { - let (exit_status, actions, steps_executed) = { - let mut backend = ExecutorState::new(storage); - let trx = Transaction { - nonce: tx_params - .nonce - .unwrap_or_else(|| storage.nonce(&tx_params.from)), - gas_price: U256::ZERO, - gas_limit: tx_params.gas_limit.unwrap_or(U256::MAX), - target: tx_params.to, - value: tx_params.value.unwrap_or_default(), - call_data: evm_loader::evm::Buffer::from_slice(&tx_params.data.unwrap_or_default()), - chain_id: Some(chain_id.into()), - ..Transaction::default() + info!("origin: {:?}", origin); + info!("tx: {:?}", tx); + + let chain_id = tx.chain_id().unwrap_or_else(|| storage.default_chain_id()); + storage.increment_nonce(origin, chain_id).await?; + + let (exit_status, steps_executed, tracer, logs) = { + let mut backend = SyncedExecutorState::new(storage); + let mut evm = match Machine::new(&tx, origin, &mut backend, tracer).await { + Ok(evm) => evm, + Err(e) => { + error!("EVM creation failed {e:?}"); + return Ok((EmulateResponse::revert(&e, &backend), None)); + } }; - let mut evm = Machine::new(trx, tx_params.from, &mut backend)?; - let (result, steps_executed) = evm.execute(step_limit, &mut backend)?; - if result == ExitStatus::StepLimit { - return Err(NeonError::TooManySteps); + let (exit_status, steps_executed, tracer) = evm.execute(step_limit, &mut backend).await?; + if exit_status == ExitStatus::StepLimit { + error!("Step_limit={step_limit} exceeded"); + return Ok(( + EmulateResponse::revert(&NeonError::TooManySteps, &backend), + None, + )); } - let actions = backend.into_actions(); - (result, actions, steps_executed) + let logs = backend.backend().logs(); + (exit_status, steps_executed, tracer, logs) }; debug!("Execute done, result={exit_status:?}"); debug!("{steps_executed} steps executed"); - let accounts_operations = storage.calc_accounts_operations(&actions); + let execute_status = storage.execute_status; - let max_iterations = (steps_executed + (EVM_STEPS_MIN - 1)) / EVM_STEPS_MIN; - let steps_gas = max_iterations * (LAMPORTS_PER_SIGNATURE + PAYMENT_TO_TREASURE); - let begin_end_gas = 2 * LAMPORTS_PER_SIGNATURE; - let actions_gas = block(storage.apply_actions(&actions)); - let accounts_gas = block(storage.apply_accounts_operations(accounts_operations)); - info!("Gas - steps: {steps_gas}, actions: {actions_gas}, accounts: {accounts_gas}"); + let steps_iterations = (steps_executed + (EVM_STEPS_MIN - 1)) / EVM_STEPS_MIN; + let treasury_gas = steps_iterations * PAYMENT_TO_TREASURE; + let cancel_gas = LAMPORTS_PER_SIGNATURE; - let (result, status) = match exit_status { - ExitStatus::Return(v) => (v, "succeed"), - ExitStatus::Revert(v) => (v, "revert"), - ExitStatus::Stop | ExitStatus::Suicide => (vec![], "succeed"), - ExitStatus::StepLimit => unreachable!(), - }; + let begin_end_iterations = 2; + let iterations: u64 = steps_iterations + begin_end_iterations + storage.realloc_iterations; + let iterations_gas = iterations * LAMPORTS_PER_SIGNATURE; + let storage_gas = storage.get_changes_in_rent()?; + + let used_gas = storage_gas + iterations_gas + treasury_gas + cancel_gas; - Ok(EmulationResult { - result, - exit_status: status.to_string(), - steps_executed, - used_gas: steps_gas + begin_end_gas + actions_gas + accounts_gas, - actions, - }) + let solana_accounts = storage + .used_accounts() + .iter() + .map(|v| SolanaAccount { + pubkey: v.pubkey, + is_writable: v.is_writable, + is_legacy: v.is_legacy, + }) + .collect::>(); + + Ok(( + EmulateResponse { + exit_status: exit_status.to_string(), + external_solana_call: execute_status.external_solana_call, + reverts_before_solana_calls: execute_status.reverts_before_solana_calls, + reverts_after_solana_calls: execute_status.reverts_after_solana_calls, + steps_executed, + used_gas, + solana_accounts, + result: exit_status.into_result().unwrap_or_default(), + iterations, + logs, + accounts_data: None, + }, + tracer.map(|tracer| tracer.into_traces(used_gas)), + )) } -pub(crate) async fn setup_syscall_stubs(rpc_client: &dyn Rpc) -> Result<(), NeonError> { - let syscall_stubs = Stubs::new(rpc_client).await?; - solana_sdk::program_stubs::set_syscall_stubs(syscall_stubs); +fn provide_account_data( + storage: &EmulatorAccountStorage, + solana_accounts: &Vec, + level: &AccountInfoLevel, +) -> Vec { + let mut accounts_data = Vec::::new(); + + for account in solana_accounts { + if !account.is_writable && AccountInfoLevel::Changed == *level { + continue; + } + + if let Some(account_data) = storage.accounts_get(&account.pubkey) { + accounts_data.push(account_data.clone()); + } + } - Ok(()) + accounts_data } diff --git a/evm_loader/lib/src/commands/get_balance.rs b/evm_loader/lib/src/commands/get_balance.rs new file mode 100644 index 000000000..009daa24f --- /dev/null +++ b/evm_loader/lib/src/commands/get_balance.rs @@ -0,0 +1,148 @@ +#![allow(clippy::future_not_send)] + +use ethnum::U256; +use evm_loader::account::legacy::LegacyEtherData; +use evm_loader::account::BalanceAccount; +use serde::{Deserialize, Serialize}; +use solana_sdk::{account::Account, pubkey::Pubkey}; + +use crate::{account_storage::account_info, rpc::Rpc, types::BalanceAddress, NeonResult}; + +use serde_with::{serde_as, DisplayFromStr}; + +use super::get_config::BuildConfigSimulator; + +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub enum BalanceStatus { + Ok, + Legacy, + Empty, +} + +#[serde_as] +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub struct GetBalanceResponse { + #[serde_as(as = "DisplayFromStr")] + pub solana_address: Pubkey, + #[serde_as(as = "DisplayFromStr")] + pub contract_solana_address: Pubkey, + pub trx_count: u64, + pub balance: U256, + pub status: BalanceStatus, +} + +impl GetBalanceResponse { + #[must_use] + pub fn empty(program_id: &Pubkey, address: &BalanceAddress) -> Self { + Self { + solana_address: address.find_pubkey(program_id), + contract_solana_address: address.find_contract_pubkey(program_id), + trx_count: 0, + balance: U256::ZERO, + status: BalanceStatus::Empty, + } + } +} + +fn read_account( + program_id: &Pubkey, + address: &BalanceAddress, + mut account: Account, +) -> NeonResult { + let solana_address = address.find_pubkey(program_id); + + let account_info = account_info(&solana_address, &mut account); + let balance_account = BalanceAccount::from_account(program_id, account_info)?; + + Ok(GetBalanceResponse { + solana_address, + contract_solana_address: address.find_contract_pubkey(program_id), + trx_count: balance_account.nonce(), + balance: balance_account.balance(), + status: BalanceStatus::Ok, + }) +} + +fn read_legacy_account( + program_id: &Pubkey, + address: &BalanceAddress, + mut account: Account, +) -> NeonResult { + let solana_address = address.find_pubkey(program_id); + let contract_solana_address = address.find_contract_pubkey(program_id); + + let account_info = account_info(&contract_solana_address, &mut account); + let balance_account = LegacyEtherData::from_account(program_id, &account_info)?; + + Ok(GetBalanceResponse { + solana_address, + contract_solana_address, + trx_count: balance_account.trx_count, + balance: balance_account.balance, + status: BalanceStatus::Legacy, + }) +} + +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + program_id: &Pubkey, + address: &[BalanceAddress], +) -> NeonResult> { + let legacy_chain_id = super::get_config::read_legacy_chain_id(rpc, *program_id).await?; + + let mut response: Vec> = vec![None; address.len()]; + let mut missing: Vec = Vec::with_capacity(address.len()); + + // Download accounts + let pubkeys: Vec<_> = address.iter().map(|a| a.find_pubkey(program_id)).collect(); + let accounts = rpc.get_multiple_accounts(&pubkeys).await?; + + for (i, account) in accounts.into_iter().enumerate() { + if let Some(account) = account { + let balance = read_account(program_id, &address[i], account)?; + response[i] = Some(balance); + } else if address[i].chain_id == legacy_chain_id { + missing.push(address[i]); + } else { + let balance = GetBalanceResponse::empty(program_id, &address[i]); + response[i] = Some(balance); + } + } + + // Download missing accounts from legacy addresses + let pubkeys: Vec<_> = missing + .iter() + .map(|a| a.find_contract_pubkey(program_id)) + .collect(); + let mut accounts = rpc.get_multiple_accounts(&pubkeys).await?; + + let mut j = 0_usize; + for i in 0..response.len() { + if response[i].is_some() { + continue; + } + + assert_eq!(address[i], missing[j]); + + let address = missing[j]; + let account = accounts[j].take(); + j += 1; + + let Some(account) = account else { + continue; + }; + let Ok(balance) = read_legacy_account(program_id, &address, account) else { + continue; + }; + response[i] = Some(balance); + } + + // Treat still missing accounts as empty + let mut result = Vec::with_capacity(response.len()); + for (i, balance) in response.into_iter().enumerate() { + let balance = balance.unwrap_or_else(|| GetBalanceResponse::empty(program_id, &address[i])); + result.push(balance); + } + + Ok(result) +} diff --git a/evm_loader/lib/src/commands/get_config.rs b/evm_loader/lib/src/commands/get_config.rs new file mode 100644 index 000000000..0cd59b5d5 --- /dev/null +++ b/evm_loader/lib/src/commands/get_config.rs @@ -0,0 +1,336 @@ +#![allow(clippy::future_not_send)] + +use std::collections::BTreeMap; + +use async_trait::async_trait; +use base64::Engine; +use enum_dispatch::enum_dispatch; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr}; +use solana_client::rpc_config::RpcSimulateTransactionConfig; +use solana_sdk::signer::Signer; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, transaction::Transaction}; +use tokio::sync::OnceCell; + +use crate::rpc::{CallDbClient, CloneRpcClient}; +use crate::solana_simulator::SolanaSimulator; +use crate::NeonResult; + +#[derive(Debug, Serialize, Deserialize)] +pub enum Status { + Ok, + Emergency, + Unknown, +} + +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ChainInfo { + pub id: u64, + pub name: String, + #[serde_as(as = "DisplayFromStr")] + pub token: Pubkey, +} + +#[serde_as] +#[derive(Debug, Serialize, Deserialize)] +pub struct GetConfigResponse { + pub version: String, + pub revision: String, + pub status: Status, + pub environment: String, + pub chains: Vec, + pub config: BTreeMap, +} + +#[allow(clippy::large_enum_variant)] +pub enum ConfigSimulator<'r> { + CloneRpcClient { + program_id: Pubkey, + rpc: &'r CloneRpcClient, + }, + ProgramTestContext { + program_id: Pubkey, + simulator: SolanaSimulator, + }, +} + +#[async_trait(?Send)] +#[enum_dispatch] +pub trait BuildConfigSimulator { + fn use_cache(&self) -> bool; + async fn build_config_simulator(&self, program_id: Pubkey) -> NeonResult; +} + +#[async_trait(?Send)] +impl BuildConfigSimulator for CloneRpcClient { + fn use_cache(&self) -> bool { + true + } + + async fn build_config_simulator(&self, program_id: Pubkey) -> NeonResult { + Ok(ConfigSimulator::CloneRpcClient { + program_id, + rpc: self, + }) + } +} + +#[async_trait(?Send)] +impl BuildConfigSimulator for CallDbClient { + fn use_cache(&self) -> bool { + false + } + + async fn build_config_simulator(&self, program_id: Pubkey) -> NeonResult { + let mut simulator = SolanaSimulator::new_without_sync(self).await?; + simulator.sync_accounts(self, &[program_id]).await?; + + Ok(ConfigSimulator::ProgramTestContext { + program_id, + simulator, + }) + } +} + +#[async_trait(?Send)] +trait ConfigInstructionSimulator { + async fn simulate_solana_instruction( + &mut self, + instruction: Instruction, + ) -> NeonResult>; +} + +#[async_trait(?Send)] +impl ConfigInstructionSimulator for &CloneRpcClient { + async fn simulate_solana_instruction( + &mut self, + instruction: Instruction, + ) -> NeonResult> { + let tx = Transaction::new_with_payer(&[instruction], Some(&self.key_for_config)); + + let result = self + .simulate_transaction_with_config( + &tx, + RpcSimulateTransactionConfig { + sig_verify: false, + replace_recent_blockhash: true, + ..RpcSimulateTransactionConfig::default() + }, + ) + .await? + .value; + + if let Some(e) = result.err { + return Err(e.into()); + } + Ok(result.logs.unwrap()) + } +} + +#[async_trait(?Send)] +impl ConfigInstructionSimulator for SolanaSimulator { + async fn simulate_solana_instruction( + &mut self, + instruction: Instruction, + ) -> NeonResult> { + let payer_pubkey = self.payer().pubkey(); + + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer_pubkey)); + transaction.message.recent_blockhash = self.blockhash(); + + let r = self.simulate_legacy_transaction(transaction)?; + if let Err(e) = r.result { + return Err(e.into()); + } + + Ok(r.logs) + } +} + +impl ConfigSimulator<'_> { + const fn program_id(&self) -> Pubkey { + match self { + ConfigSimulator::CloneRpcClient { program_id, .. } + | ConfigSimulator::ProgramTestContext { program_id, .. } => *program_id, + } + } + + async fn simulate_evm_instruction( + &mut self, + evm_instruction: u8, + data: &[u8], + ) -> NeonResult> { + fn base64_decode(s: &str) -> Vec { + base64::engine::general_purpose::STANDARD.decode(s).unwrap() + } + + let program_id = self.program_id(); + + let logs = self + .simulate_solana_instruction(Instruction::new_with_bytes( + program_id, + &[&[evm_instruction], data].concat(), + vec![], + )) + .await?; + + // Program return: 53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io AQAAAAAAAAA= + let return_data = logs + .into_iter() + .find_map(|msg| { + let prefix = std::format!("Program return: {program_id} "); + msg.strip_prefix(&prefix).map(base64_decode) + }) + .unwrap(); + + Ok(return_data) + } + + async fn simulate_solana_instruction( + &mut self, + instruction: Instruction, + ) -> NeonResult> { + match self { + ConfigSimulator::CloneRpcClient { rpc, .. } => { + rpc.simulate_solana_instruction(instruction).await + } + ConfigSimulator::ProgramTestContext { simulator, .. } => { + simulator.simulate_solana_instruction(instruction).await + } + } + } + + async fn get_version(&mut self) -> NeonResult<(String, String)> { + let return_data = self.simulate_evm_instruction(0xA7, &[]).await?; + let (version, revision) = bincode::deserialize(&return_data)?; + + Ok((version, revision)) + } + + async fn get_status(&mut self) -> NeonResult { + let return_data = self.simulate_evm_instruction(0xA6, &[]).await?; + match return_data.first() { + Some(0) => Ok(Status::Emergency), + Some(1) => Ok(Status::Ok), + _ => Ok(Status::Unknown), + } + } + + async fn get_environment(&mut self) -> NeonResult { + let return_data = self.simulate_evm_instruction(0xA2, &[]).await?; + let environment = String::from_utf8(return_data)?; + + Ok(environment) + } + + async fn get_chains(&mut self) -> NeonResult> { + let mut result = Vec::new(); + + let return_data = self.simulate_evm_instruction(0xA0, &[]).await?; + let chain_count = return_data.as_slice().try_into()?; + let chain_count = usize::from_le_bytes(chain_count); + + for i in 0..chain_count { + let index = i.to_le_bytes(); + let return_data = self.simulate_evm_instruction(0xA1, &index).await?; + + let (id, name, token) = bincode::deserialize(&return_data)?; + result.push(ChainInfo { id, name, token }); + } + + Ok(result) + } + + async fn get_properties(&mut self) -> NeonResult> { + let mut result = BTreeMap::new(); + + let return_data = self.simulate_evm_instruction(0xA3, &[]).await?; + let count = return_data.as_slice().try_into()?; + let count = usize::from_le_bytes(count); + + for i in 0..count { + let index = i.to_le_bytes(); + let return_data = self.simulate_evm_instruction(0xA4, &index).await?; + + let (name, value) = bincode::deserialize(&return_data)?; + result.insert(name, value); + } + + Ok(result) + } +} + +pub async fn execute( + rpc: &impl BuildConfigSimulator, + program_id: Pubkey, +) -> NeonResult { + let mut simulator = rpc.build_config_simulator(program_id).await?; + + let (version, revision) = simulator.get_version().await?; + + Ok(GetConfigResponse { + version, + revision, + status: simulator.get_status().await?, + environment: simulator.get_environment().await?, + chains: simulator.get_chains().await?, + config: simulator.get_properties().await?, + }) +} + +static CHAINS_CACHE: OnceCell> = OnceCell::const_new(); + +pub async fn read_chains( + rpc: &impl BuildConfigSimulator, + program_id: Pubkey, +) -> NeonResult> { + if rpc.use_cache() { + return CHAINS_CACHE + .get_or_try_init(|| get_chains(rpc, program_id)) + .await + .cloned(); + } + + get_chains(rpc, program_id).await +} + +async fn get_chains( + rpc: &(impl BuildConfigSimulator + Sized), + program_id: Pubkey, +) -> NeonResult> { + rpc.build_config_simulator(program_id) + .await? + .get_chains() + .await +} + +pub async fn read_legacy_chain_id( + rpc: &impl BuildConfigSimulator, + program_id: Pubkey, +) -> NeonResult { + for chain in read_chains(rpc, program_id).await? { + if chain.name == "neon" { + return Ok(chain.id); + } + } + + unreachable!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bpf_loader_pubkey() { + let pubkey = Pubkey::from([ + 2, 168, 246, 145, 78, 136, 161, 110, 57, 90, 225, 40, 148, 143, 250, 105, 86, 147, 55, + 104, 24, 221, 71, 67, 82, 33, 243, 198, 0, 0, 0, 0, + ]); + assert_eq!( + format!("{pubkey}"), + "BPFLoader2111111111111111111111111111111111" + ); + } +} diff --git a/evm_loader/lib/src/commands/get_contract.rs b/evm_loader/lib/src/commands/get_contract.rs new file mode 100644 index 000000000..7d6b8ccb1 --- /dev/null +++ b/evm_loader/lib/src/commands/get_contract.rs @@ -0,0 +1,102 @@ +use evm_loader::{ + account::{legacy::LegacyEtherData, ContractAccount}, + types::Address, +}; +use serde::{Deserialize, Serialize}; +use solana_sdk::{account::Account, pubkey::Pubkey}; + +use crate::{account_storage::account_info, rpc::Rpc, NeonResult}; + +use serde_with::{hex::Hex, serde_as, DisplayFromStr}; + +use super::get_config::BuildConfigSimulator; + +#[serde_as] +#[derive(Debug, Serialize, Deserialize)] +pub struct GetContractResponse { + #[serde_as(as = "DisplayFromStr")] + pub solana_address: Pubkey, + pub chain_id: Option, + #[serde_as(as = "Hex")] + pub code: Vec, +} + +impl GetContractResponse { + #[must_use] + pub const fn empty(solana_address: Pubkey) -> Self { + Self { + solana_address, + chain_id: None, + code: vec![], + } + } +} + +fn read_legacy_account( + program_id: &Pubkey, + legacy_chain_id: u64, + solana_address: Pubkey, + mut account: Account, +) -> GetContractResponse { + let account_info = account_info(&solana_address, &mut account); + let Ok(contract) = LegacyEtherData::from_account(program_id, &account_info) else { + return GetContractResponse::empty(solana_address); + }; + + let chain_id = Some(legacy_chain_id); + let code = contract.read_code(&account_info); + + GetContractResponse { + solana_address, + chain_id, + code, + } +} + +fn read_account( + program_id: &Pubkey, + legacy_chain_id: u64, + solana_address: Pubkey, + account: Option, +) -> GetContractResponse { + let Some(mut account) = account else { + return GetContractResponse::empty(solana_address); + }; + + let account_info = account_info(&solana_address, &mut account); + let Ok(contract) = ContractAccount::from_account(program_id, account_info) else { + return read_legacy_account(program_id, legacy_chain_id, solana_address, account); + }; + + let chain_id = Some(contract.chain_id()); + let code = contract.code().to_vec(); + + GetContractResponse { + solana_address, + chain_id, + code, + } +} + +pub async fn execute( + rpc: &(impl Rpc + BuildConfigSimulator), + program_id: &Pubkey, + accounts: &[Address], +) -> NeonResult> { + let legacy_chain_id = super::get_config::read_legacy_chain_id(rpc, *program_id).await?; + + let pubkeys: Vec<_> = accounts + .iter() + .map(|a| a.find_solana_address(program_id).0) + .collect(); + + let accounts = rpc.get_multiple_accounts(&pubkeys).await?; + + let mut result = Vec::with_capacity(accounts.len()); + for (key, account) in pubkeys.into_iter().zip(accounts) { + let response = read_account(program_id, legacy_chain_id, key, account); + result.push(response); + } + + Ok(result) +} diff --git a/evm_loader/lib/src/commands/get_ether_account_data.rs b/evm_loader/lib/src/commands/get_ether_account_data.rs deleted file mode 100644 index 31b98506e..000000000 --- a/evm_loader/lib/src/commands/get_ether_account_data.rs +++ /dev/null @@ -1,70 +0,0 @@ -use evm_loader::{account::EthereumAccount, types::Address}; -use serde::{Deserialize, Serialize}; -use solana_sdk::pubkey::Pubkey; -use std::fmt::{Display, Formatter}; - -use crate::{ - account_storage::{account_info, EmulatorAccountStorage}, - errors::NeonError, - rpc::Rpc, - NeonResult, -}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct GetEtherAccountDataReturn { - pub solana_address: String, - pub address: Address, - pub bump_seed: u8, - pub trx_count: u64, - pub rw_blocked: bool, - pub balance: String, - pub generation: u32, - pub code_size: u32, - pub code: String, -} - -impl Display for GetEtherAccountDataReturn { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{{ address: {}, solana_address: {}, trx_count: {}, balance: {}, generation: {}, code_size: {} }}", - self.address, - self.solana_address, - self.trx_count, - self.balance, - self.generation, - self.code_size, - ) - } -} - -pub async fn execute( - rpc_client: &dyn Rpc, - evm_loader: &Pubkey, - ether_address: &Address, -) -> NeonResult { - match EmulatorAccountStorage::get_account_from_solana(rpc_client, evm_loader, ether_address) - .await - { - (solana_address, Some(mut acc)) => { - let acc_info = account_info(&solana_address, &mut acc); - let account_data = EthereumAccount::from_account(evm_loader, &acc_info).unwrap(); - let contract_code = account_data - .contract_data() - .map_or_else(Vec::new, |c| c.code().to_vec()); - - Ok(GetEtherAccountDataReturn { - solana_address: solana_address.to_string(), - address: account_data.address, - bump_seed: account_data.bump_seed, - trx_count: account_data.trx_count, - rw_blocked: account_data.rw_blocked, - balance: account_data.balance.to_string(), - generation: account_data.generation, - code_size: account_data.code_size, - code: hex::encode(contract_code), - }) - } - (solana_address, None) => Err(NeonError::AccountNotFound(solana_address)), - } -} diff --git a/evm_loader/lib/src/commands/get_holder.rs b/evm_loader/lib/src/commands/get_holder.rs new file mode 100644 index 000000000..d4bdf3dce --- /dev/null +++ b/evm_loader/lib/src/commands/get_holder.rs @@ -0,0 +1,178 @@ +use ethnum::U256; +use evm_loader::account::{ + legacy::{ + LegacyFinalizedData, LegacyHolderData, TAG_HOLDER_DEPRECATED, + TAG_STATE_FINALIZED_DEPRECATED, + }, + Holder, StateAccount, StateFinalizedAccount, TAG_HOLDER, TAG_STATE, TAG_STATE_FINALIZED, +}; +use serde::{Deserialize, Serialize}; +use solana_sdk::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; +use std::fmt::Display; + +use crate::{account_storage::account_info, rpc::Rpc, types::Address, types::TxParams, NeonResult}; + +use serde_with::{hex::Hex, serde_as, skip_serializing_none, DisplayFromStr}; + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub enum Status { + #[default] + Empty, + Error(String), + Holder, + Active, + Finalized, +} + +#[serde_as] +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct AccountMeta { + pub is_writable: bool, + #[serde_as(as = "DisplayFromStr")] + pub key: Pubkey, +} + +#[serde_as] +#[skip_serializing_none] +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct GetHolderResponse { + pub status: Status, + pub len: Option, + #[serde_as(as = "Option")] + pub owner: Option, + + #[serde_as(as = "Option")] + pub tx: Option<[u8; 32]>, + pub tx_data: Option, + pub tx_type: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub chain_id: Option, + pub origin: Option

, + + #[serde_as(as = "Option>")] + pub accounts: Option>, + + pub steps_executed: u64, +} + +impl GetHolderResponse { + #[must_use] + pub fn empty() -> Self { + Self { + status: Status::Empty, + ..Self::default() + } + } + + pub fn error(error: T) -> Self { + Self { + status: Status::Error(error.to_string()), + ..Self::default() + } + } +} + +pub fn read_holder(program_id: &Pubkey, info: AccountInfo) -> NeonResult { + let data_len = info.data_len(); + + match evm_loader::account::tag(program_id, &info)? { + TAG_HOLDER => { + let holder = Holder::from_account(program_id, info)?; + + Ok(GetHolderResponse { + status: Status::Holder, + len: Some(data_len), + owner: Some(holder.owner()), + tx: Some(holder.transaction_hash()), + // Holder may not yet contain the transaction and empty rlp panics. + // TODO: check the behavior. + tx_type: Some(0), + ..GetHolderResponse::default() + }) + } + TAG_HOLDER_DEPRECATED => { + let holder = LegacyHolderData::from_account(program_id, &info)?; + Ok(GetHolderResponse { + status: Status::Holder, + len: Some(data_len), + owner: Some(holder.owner), + tx: Some([0u8; 32]), + // Deprecated holders can't use new transaction type, because new transaction type + // is being supported much later than such holder because deprecated. + // Thus, tx_type=0 (legacy), max_fee_per_gas and max_priority_fee_per_gas is None. + tx_type: Some(0), + ..GetHolderResponse::default() + }) + } + TAG_STATE_FINALIZED => { + let state = StateFinalizedAccount::from_account(program_id, info)?; + Ok(GetHolderResponse { + status: Status::Finalized, + len: Some(data_len), + owner: Some(state.owner()), + tx: Some(state.trx_hash()), + // transaction_type, max_fee_per_gas and max_priority_fee_per_gas are not needed + // when transaction is already finalized. + // Also, the data about transaction is already not in the holder anymore. + // We explicitly set tx_type=0 to indicate that there shouldn't be new gas params. + tx_type: Some(0), + ..GetHolderResponse::default() + }) + } + TAG_STATE_FINALIZED_DEPRECATED => { + let state = LegacyFinalizedData::from_account(program_id, &info)?; + Ok(GetHolderResponse { + status: Status::Finalized, + len: Some(data_len), + owner: Some(state.owner), + tx: Some(state.transaction_hash), + // transaction_type, max_fee_per_gas and max_priority_fee_per_gas are not needed + // when transaction is already finalized. + // Also, the data about transaction is already not in the holder anymore. + // We explicitly set tx_type=0 to indicate that there shouldn't be new gas params. + tx_type: Some(0), + ..GetHolderResponse::default() + }) + } + TAG_STATE => { + // StateAccount::from_account doesn't work here because state contains heap + // and transaction inside state account has been allocated via this heap. + // Data should be read by pointers with offsets. + let (transaction, owner, origin, accounts, steps) = + StateAccount::get_state_account_view(program_id, &info)?; + + let tx_params = TxParams::from_transaction(origin, &transaction); + + Ok(GetHolderResponse { + status: Status::Active, + len: Some(data_len), + owner: Some(owner), + tx: Some(transaction.hash()), + tx_data: Some(tx_params), + tx_type: Some(transaction.tx_type()), + max_fee_per_gas: transaction.max_fee_per_gas(), + max_priority_fee_per_gas: transaction.max_priority_fee_per_gas(), + chain_id: transaction.chain_id(), + origin: Some(origin), + accounts: Some(accounts), + steps_executed: steps, + }) + } + _ => Err(ProgramError::InvalidAccountData.into()), + } +} + +pub async fn execute( + rpc: &impl Rpc, + program_id: &Pubkey, + address: Pubkey, +) -> NeonResult { + let response = rpc.get_account(&address).await?; + let Some(mut account) = response else { + return Ok(GetHolderResponse::empty()); + }; + + let info = account_info(&address, &mut account); + Ok(read_holder(program_id, info).unwrap_or_else(GetHolderResponse::error)) +} diff --git a/evm_loader/lib/src/commands/get_neon_elf.rs b/evm_loader/lib/src/commands/get_neon_elf.rs index 54aada9f6..2d3b78477 100644 --- a/evm_loader/lib/src/commands/get_neon_elf.rs +++ b/evm_loader/lib/src/commands/get_neon_elf.rs @@ -1,13 +1,14 @@ -use std::{collections::HashMap, convert::TryFrom, fs::File, io::Read}; - +use anyhow::{Context as AContext, Result}; use solana_sdk::{ account_utils::StateMut, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, pubkey::Pubkey, }; +use std::{collections::HashMap, convert::TryFrom, fs::File, io::Read}; -use crate::{context::Context, errors::NeonError, Config, NeonResult}; +use crate::rpc::Rpc; +use crate::{errors::NeonError, Config, NeonResult}; pub type GetNeonElfReturn = HashMap; @@ -16,18 +17,20 @@ pub struct CachedElfParams { } impl CachedElfParams { - pub async fn new(config: &Config, context: &Context) -> Self { + pub async fn new(config: &Config, rpc: &impl Rpc) -> Self { Self { - elf_params: read_elf_parameters_from_account(config, context) + elf_params: read_elf_parameters_from_account(config, rpc) .await .expect("read elf_params error"), } } + #[must_use] pub fn get(&self, param_name: &str) -> Option<&String> { self.elf_params.get(param_name) } } +#[must_use] pub fn read_elf_parameters(_config: &Config, program_data: &[u8]) -> GetNeonElfReturn { let mut result = HashMap::new(); let elf = goblin::elf::Elf::parse(program_data).expect("Unable to parse ELF file"); @@ -82,25 +85,91 @@ pub fn read_elf_parameters(_config: &Config, program_data: &[u8]) -> GetNeonElfR result } +pub fn get_elf_parameter(data: &[u8], elf_parameter: &str) -> Result { + let offset = UpgradeableLoaderState::size_of_programdata_metadata(); + + // Check if the offset is within the bounds of `data` + if data.len() <= offset { + let error_msg = format!( + "Offset beyond data bounds. Data len: {}, offset: {offset}, data bytes: {data:?}", + data.len(), + ); + return Err(anyhow::anyhow!(error_msg)); + } + + let program_data = &data[offset..]; + + let elf = goblin::elf::Elf::parse(program_data).context("Unable to parse ELF file")?; + let ctx = goblin::container::Ctx::new( + if elf.is_64 { + goblin::container::Container::Big + } else { + goblin::container::Container::Little + }, + if elf.little_endian { + scroll::Endian::Little + } else { + scroll::Endian::Big + }, + ); + + let (num_syms, offset) = elf + .section_headers + .into_iter() + .find(|section| section.sh_type == goblin::elf::section_header::SHT_DYNSYM) + .map(|section| (section.sh_size / section.sh_entsize, section.sh_offset)) + .ok_or_else(|| anyhow::anyhow!("SHT_DYNSYM section not found"))?; + + let dynsyms = goblin::elf::Symtab::parse( + program_data, + offset.try_into().context("Offset too large")?, + num_syms.try_into().context("Count too large")?, + ctx, + ) + .context("Error parsing Symtab")?; + + for sym in &dynsyms { + let name = &elf.dynstrtab[sym.st_name]; + if name == elf_parameter { + let end = program_data.len(); + let from: usize = usize::try_from(sym.st_value) + .map_err(|_| anyhow::anyhow!("Unable to cast usize from u64:{:?}", sym.st_value))?; + let to: usize = usize::try_from(sym.st_value + sym.st_size).map_err(|err| { + anyhow::anyhow!( + "Unable to cast usize from u64:{:?}. Error: {err}", + sym.st_value + sym.st_size + ) + })?; + + if to < end && from < end { + let buf = &program_data[from..to]; + let value = std::str::from_utf8(buf).context("Read ELF value error")?; + return Ok(String::from(value)); + } + + return Err(anyhow::anyhow!("{name} is out of bounds")); + } + } + + Err(anyhow::anyhow!("ELF parameter not found")) +} + pub async fn read_elf_parameters_from_account( config: &Config, - context: &Context, + rpc: &impl Rpc, ) -> Result { - let (_, program_data) = - read_program_data_from_account(config, context, &config.evm_loader).await?; + let (_, program_data) = read_program_data_from_account(config, rpc, &config.evm_loader).await?; Ok(read_elf_parameters(config, &program_data)) } pub async fn read_program_data_from_account( config: &Config, - context: &Context, + rpc: &impl Rpc, evm_loader: &Pubkey, ) -> Result<(Option, Vec), NeonError> { - let account = context - .rpc_client - .get_account_with_commitment(evm_loader, config.commitment) + let account = rpc + .get_account(evm_loader) .await? - .value .ok_or(NeonError::AccountNotFound(*evm_loader))?; if account.owner == bpf_loader::id() || account.owner == bpf_loader_deprecated::id() { @@ -110,15 +179,9 @@ pub async fn read_program_data_from_account( programdata_address, }) = account.state() { - let programdata_account = context - .rpc_client - .get_account_with_commitment(&programdata_address, config.commitment) - .await? - .value - .ok_or(NeonError::AssociatedPdaNotFound( - programdata_address, - config.evm_loader, - ))?; + let programdata_account = rpc.get_account(&programdata_address).await?.ok_or( + NeonError::AssociatedPdaNotFound(programdata_address, config.evm_loader), + )?; if let Ok(UpgradeableLoaderState::ProgramData { upgrade_authority_address, @@ -167,19 +230,19 @@ fn read_program_params_from_file( async fn read_program_params_from_account( config: &Config, - context: &Context, + rpc: &impl Rpc, ) -> NeonResult { - read_elf_parameters_from_account(config, context).await + read_elf_parameters_from_account(config, rpc).await } pub async fn execute( config: &Config, - context: &Context, + rpc: &impl Rpc, program_location: Option<&str>, ) -> NeonResult { if let Some(program_location) = program_location { read_program_params_from_file(config, program_location) } else { - read_program_params_from_account(config, context).await + read_program_params_from_account(config, rpc).await } } diff --git a/evm_loader/lib/src/commands/get_storage_at.rs b/evm_loader/lib/src/commands/get_storage_at.rs index 7a33b3117..e46e5ac56 100644 --- a/evm_loader/lib/src/commands/get_storage_at.rs +++ b/evm_loader/lib/src/commands/get_storage_at.rs @@ -1,82 +1,26 @@ -use std::convert::TryInto; -use std::fmt::{Display, Formatter}; - use ethnum::U256; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use evm_loader::account::EthereumAccount; -use evm_loader::{ - account::{ether_storage::EthereumStorageAddress, EthereumStorage}, - config::STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT, - types::Address, -}; +use evm_loader::{account_storage::AccountStorage, types::Address}; -use crate::{ - account_storage::{account_info, EmulatorAccountStorage}, - rpc::Rpc, - types::block, - NeonResult, -}; +use crate::commands::get_config::BuildConfigSimulator; +use crate::rpc::Rpc; +use crate::{account_storage::EmulatorAccountStorage, NeonResult}; -#[derive(Default, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct GetStorageAtReturn(pub [u8; 32]); -impl Display for GetStorageAtReturn { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "0x{}", hex::encode(&self.0)) - } -} - pub async fn execute( - rpc_client: &dyn Rpc, - evm_loader: &Pubkey, - ether_address: Address, - index: &U256, + rpc: &(impl Rpc + BuildConfigSimulator), + program_id: &Pubkey, + address: Address, + index: U256, ) -> NeonResult { - let value = if let (solana_address, Some(mut account)) = - EmulatorAccountStorage::get_account_from_solana(rpc_client, evm_loader, ðer_address) - .await - { - let info = account_info(&solana_address, &mut account); - - let account_data = EthereumAccount::from_account(evm_loader, &info)?; - if let Some(contract) = account_data.contract_data() { - if *index < U256::from(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT) { - let index: usize = index.as_usize() * 32; - GetStorageAtReturn(contract.storage()[index..index + 32].try_into().unwrap()) - } else { - let subindex = (*index & 0xFF).as_u8(); - let index = *index & !U256::new(0xFF); - - let address = - EthereumStorageAddress::new(evm_loader, account_data.info.key, &index); - - if let Ok(mut account) = block(rpc_client.get_account(address.pubkey())) { - if solana_sdk::system_program::check_id(&account.owner) { - Default::default() - } else { - let account_info = account_info(address.pubkey(), &mut account); - let storage = EthereumStorage::from_account(evm_loader, &account_info)?; - if (storage.address != ether_address) - || (storage.index != index) - || (storage.generation != account_data.generation) - { - Default::default() - } else { - GetStorageAtReturn(storage.get(subindex)) - } - } - } else { - Default::default() - } - } - } else { - Default::default() - } - } else { - Default::default() - }; + let value = EmulatorAccountStorage::new(rpc, *program_id, None, None, None, None, None) + .await? + .storage(address, index) + .await; - Ok(value) + Ok(GetStorageAtReturn(value)) } diff --git a/evm_loader/lib/src/commands/init_environment.rs b/evm_loader/lib/src/commands/init_environment.rs index ee7d87f46..78d2c0c52 100644 --- a/evm_loader/lib/src/commands/init_environment.rs +++ b/evm_loader/lib/src/commands/init_environment.rs @@ -1,9 +1,10 @@ -use std::sync::Arc; +use std::rc::Rc; use serde::{Deserialize, Serialize}; -use crate::{context::Context, NeonResult}; +use crate::NeonResult; +use crate::rpc::CloneRpcClient; use { crate::{ commands::{ @@ -46,7 +47,7 @@ struct Parameters { } impl Parameters { - pub fn new(params: HashMap) -> Self { + pub const fn new(params: HashMap) -> Self { Self { params } } @@ -103,29 +104,24 @@ fn read_keys_dir(keys_dir: &str) -> Result, NeonError> #[allow(clippy::too_many_lines)] pub async fn execute( config: &Config, - context: &Context, + client: &CloneRpcClient, + signer: &dyn Signer, send_trx: bool, force: bool, keys_dir: Option<&str>, file: Option<&str>, ) -> NeonResult { - let signer = context.signer()?; info!( "Signer: {}, send_trx: {}, force: {}", signer.pubkey(), send_trx, force ); - let second_signer: Arc = Arc::from(context.signer()?); - let fee_payer = config + let fee_payer: &dyn Signer = config .fee_payer .as_ref() - .map_or_else(move || second_signer, |v| v.clone()); - let executor = Arc::new(TransactionExecutor::new( - context.rpc_client.clone(), - fee_payer, - send_trx, - )); + .map_or(signer, |fee_payer| fee_payer); + let executor = Rc::new(TransactionExecutor::new(client, fee_payer, send_trx)); let keys = keys_dir.map_or(Ok(HashMap::new()), read_keys_dir)?; let program_data_address = Pubkey::find_program_address( @@ -134,19 +130,20 @@ pub async fn execute( ) .0; let (program_upgrade_authority, program_data) = - read_program_data_from_account(config, context, &config.evm_loader).await?; + read_program_data_from_account(config, client, &config.evm_loader).await?; let data = file.map_or(Ok(program_data), read_program_data)?; let program_parameters = Parameters::new(read_elf_parameters(config, &data)); let neon_revision = program_parameters.get::("NEON_REVISION")?; - if neon_revision != env!("NEON_REVISION") { + let env_neon_revision = env!("NEON_REVISION"); + if neon_revision != env_neon_revision { if force { warn!("NeonEVM revision doesn't match CLI revision. This check has been disabled with `--force` flag"); } else { error!("NeonEVM revision doesn't match CLI revision. Use appropriate neon-cli version or add `--force` flag"); return Err(EnvironmentError::RevisionMismatch( neon_revision, - env!("NEON_REVISION").to_string(), + env_neon_revision.to_string(), ) .into()); } @@ -154,14 +151,12 @@ pub async fn execute( //====================== Create NEON-token mint =================================================================== let executor_clone = executor.clone(); - let second_signer = context.signer()?; let create_token = move |mint: Pubkey, decimals: u8| async move { let mint_signer = keys .get(&mint) .ok_or(EnvironmentError::MissingPrivateKey(mint))?; let data_len = spl_token::state::Mint::LEN; - let lamports = context - .rpc_client + let lamports = client .get_minimum_balance_for_rent_exemption(data_len) .await?; let parameters = &[ @@ -175,7 +170,7 @@ pub async fn execute( spl_token::instruction::initialize_mint2( &spl_token::id(), &mint, - &second_signer.pubkey(), + &signer.pubkey(), None, decimals, )?, @@ -186,8 +181,8 @@ pub async fn execute( Ok(Some(transaction)) }; - let neon_token_mint = program_parameters.get::("NEON_TOKEN_MINT")?; - let neon_token_mint_decimals = program_parameters.get::("NEON_TOKEN_MINT_DECIMALS")?; + let neon_token_mint: Pubkey = program_parameters.get("NEON_TOKEN_MINT")?; + let neon_token_mint_decimals = 9; executor .check_and_create_object( "NEON-token mint", @@ -209,41 +204,42 @@ pub async fn execute( //====================== Create 'Deposit' NEON-token balance ====================================================== let (deposit_authority, _) = Pubkey::find_program_address(&[b"Deposit"], &config.evm_loader); - let deposit_address = get_associated_token_address(&deposit_authority, &neon_token_mint); - executor - .check_and_create_object( - "NEON Deposit balance", - executor - .get_account_data_pack::( - &spl_token::id(), - &deposit_address, - ) - .await, - |account| async move { - if account.mint != neon_token_mint || account.owner != deposit_authority { - Err(EnvironmentError::InvalidSplTokenAccount(deposit_address).into()) - } else { - Ok(None) - } - }, - || async { - let transaction = executor + let chains = super::get_config::read_chains(client, config.evm_loader).await?; + for chain in chains { + let pool = get_associated_token_address(&deposit_authority, &chain.token); + + executor + .check_and_create_object( + "Token pool account", + executor + .get_account_data_pack::(&spl_token::id(), &pool) + .await, + |account| async move { + if account.mint != chain.token || account.owner != deposit_authority { + Err(EnvironmentError::InvalidSplTokenAccount(pool).into()) + } else { + Ok(None) + } + }, + || async { + let transaction = executor .create_transaction_with_payer_only(&[ spl_associated_token_account::instruction::create_associated_token_account( &executor.fee_payer.pubkey(), &deposit_authority, - &neon_token_mint, + &chain.token, &spl_token::id(), ), ]) .await?; - Ok(Some(transaction)) - }, - ) - .await?; + Ok(Some(transaction)) + }, + ) + .await?; + } //====================== Create main treasury balance ============================================================= - let treasury_pool_seed = program_parameters.get::("NEON_POOL_SEED")?; + let treasury_pool_seed = program_parameters.get::("NEON_TREASURY_POOL_SEED")?; if treasury_pool_seed != TREASURY_POOL_SEED { error!( "Treasury pool seed mismatch {} != {}", @@ -276,7 +272,7 @@ pub async fn execute( AccountMeta::new(executor.fee_payer.pubkey(), true), ], )], - &[&*signer], + &[signer], ) .await?; Ok(Some(transaction)) @@ -285,12 +281,9 @@ pub async fn execute( .await?; //====================== Create auxiliary treasury balances ======================================================= - let treasury_pool_count = program_parameters.get::("NEON_POOL_COUNT")?; + let treasury_pool_count = program_parameters.get::("NEON_TREASURY_POOL_COUNT")?; for i in 0..treasury_pool_count { - let minimum_balance = context - .rpc_client - .get_minimum_balance_for_rent_exemption(0) - .await?; + let minimum_balance = client.get_minimum_balance_for_rent_exemption(0).await?; let aux_balance_address = Treasury::address(&config.evm_loader, i).0; let executor_clone = executor.clone(); executor @@ -325,15 +318,16 @@ pub async fn execute( .await?; } - executor.checkpoint(context.rpc_client.commitment()).await?; + executor.checkpoint(client.commitment()).await?; - let stats = executor.stats.read().await; - info!("Stats: {:?}", stats); + { + let stats = executor.stats.borrow(); + info!("Stats: {:?}", stats); + } let signatures = executor .signatures - .read() - .await + .borrow() .iter() .map(|s| bs58::encode(s).into_string()) .collect::>(); @@ -342,6 +336,8 @@ pub async fn execute( transactions: signatures, }; + let stats = executor.stats.borrow(); + if stats.total_objects == stats.corrected_objects { Ok(result) } else if stats.invalid_objects == 0 { diff --git a/evm_loader/lib/src/commands/mod.rs b/evm_loader/lib/src/commands/mod.rs index e99c6dcdc..7cf084b97 100644 --- a/evm_loader/lib/src/commands/mod.rs +++ b/evm_loader/lib/src/commands/mod.rs @@ -1,4 +1,4 @@ -use crate::rpc::Rpc; +use solana_client::nonblocking::rpc_client::RpcClient; use solana_client::{ client_error::Result as SolanaClientResult, rpc_config::RpcSendTransactionConfig, }; @@ -11,20 +11,21 @@ use solana_sdk::{ transaction::Transaction, }; -pub mod cancel_trx; pub mod collect_treasury; -pub mod create_ether_account; -pub mod deposit; pub mod emulate; -pub mod get_ether_account_data; +pub mod get_balance; +pub mod get_config; +pub mod get_contract; +pub mod get_holder; pub mod get_neon_elf; pub mod get_storage_at; pub mod init_environment; +pub mod simulate_solana; pub mod trace; mod transaction_executor; pub async fn send_transaction( - rpc_client: &dyn Rpc, + rpc_client: &RpcClient, signer: &dyn Signer, instructions: &[Instruction], ) -> SolanaClientResult { diff --git a/evm_loader/lib/src/commands/simulate_solana.rs b/evm_loader/lib/src/commands/simulate_solana.rs new file mode 100644 index 000000000..64fc69763 --- /dev/null +++ b/evm_loader/lib/src/commands/simulate_solana.rs @@ -0,0 +1,141 @@ +use std::collections::HashSet; + +use crate::{ + rpc::Rpc, + solana_simulator::{SolanaSimulator, SyncState}, + types::SimulateSolanaRequest, + NeonResult, +}; +use bincode::Options; +use log::info; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use solana_program_runtime::compute_budget::ComputeBudget; +use solana_runtime::runtime_config::RuntimeConfig; +use solana_sdk::{ + pubkey::Pubkey, + transaction::{SanitizedTransaction, Transaction, VersionedTransaction}, +}; +use solana_transaction_status::EncodableWithMeta; + +#[serde_as] +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct SimulateSolanaTransactionResult { + pub error: Option, + pub logs: Vec, + pub executed_units: u64, +} + +#[serde_as] +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct SimulateSolanaResponse { + transactions: Vec, +} + +fn decode_transaction(data: &[u8]) -> NeonResult { + let tx_result = bincode::options() + .with_fixint_encoding() + .allow_trailing_bytes() + .deserialize::(data); + + if let Ok(tx) = tx_result { + return Ok(tx); + } + + let tx = bincode::options() + .with_fixint_encoding() + .allow_trailing_bytes() + .deserialize::(data)?; + + Ok(tx.into()) +} + +fn address_table_lookups(txs: &[VersionedTransaction]) -> Vec { + let mut accounts: HashSet = HashSet::::new(); + for tx in txs { + let Some(address_table_lookups) = tx.message.address_table_lookups() else { + continue; + }; + + for alt in address_table_lookups { + accounts.insert(alt.account_key); + } + } + + accounts.into_iter().collect() +} + +fn account_keys(txs: &[SanitizedTransaction]) -> Vec { + let mut accounts: HashSet = HashSet::::new(); + for tx in txs { + let keys = tx.message().account_keys(); + accounts.extend(keys.iter()); + } + + accounts.into_iter().collect() +} + +fn runtime_config(request: &SimulateSolanaRequest) -> RuntimeConfig { + let compute_units = request.compute_units.unwrap_or(1_400_000); + let heap_size = request.heap_size.unwrap_or(256 * 1024); + + let mut compute_budget = ComputeBudget::new(compute_units); + compute_budget.heap_size = heap_size; + + RuntimeConfig { + compute_budget: Some(compute_budget), + log_messages_bytes_limit: Some(100 * 1024), + transaction_account_lock_limit: request.account_limit, + } +} + +pub async fn execute( + rpc: &impl Rpc, + request: SimulateSolanaRequest, +) -> NeonResult { + let verify = request.verify.unwrap_or(true); + let config = runtime_config(&request); + + let mut simulator = SolanaSimulator::new_with_config(rpc, config, SyncState::Yes).await?; + + // Decode transactions from bytes + let mut transactions: Vec = vec![]; + for data in request.transactions { + let tx = decode_transaction(&data)?; + info!( + "Encoded transaction: {}", + serde_json::to_string(&tx.json_encode()).unwrap() + ); + transactions.push(tx); + } + + // Download ALT + let alt = address_table_lookups(&transactions); + simulator.sync_accounts(rpc, &alt).await?; + + // Sanitize transactions (verify tx and decode ALT) + let mut sanitized_transactions: Vec = vec![]; + for tx in transactions { + let sanitized = simulator.sanitize_transaction(tx, verify)?; + sanitized_transactions.push(sanitized); + } + + // Download accounts + let accounts = account_keys(&sanitized_transactions); + simulator.sync_accounts(rpc, &accounts).await?; + + // Process transactions + let mut results = Vec::new(); + for tx in sanitized_transactions { + let r = simulator.process_transaction(request.blockhash.into(), &tx)?; + results.push(SimulateSolanaTransactionResult { + error: r.result.err(), + logs: r.logs, + executed_units: r.units_consumed, + }); + } + + Ok(SimulateSolanaResponse { + transactions: results, + }) +} diff --git a/evm_loader/lib/src/commands/trace.rs b/evm_loader/lib/src/commands/trace.rs index 1c7a03ff4..1a243421e 100644 --- a/evm_loader/lib/src/commands/trace.rs +++ b/evm_loader/lib/src/commands/trace.rs @@ -1,129 +1,32 @@ -use crate::{ - account_storage::EmulatorAccountStorage, - commands::emulate::{emulate_transaction, emulate_trx, setup_syscall_stubs}, - errors::NeonError, - event_listener::tracer::Tracer, - rpc::Rpc, - types::{ - trace::{TraceCallConfig, TraceConfig, TracedCall}, - TxParams, - }, -}; -use evm_loader::types::Address; -use serde::{Deserialize, Serialize}; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; -use std::fmt::{Display, Formatter}; +#![allow(clippy::missing_errors_doc)] -#[allow(clippy::too_many_arguments)] -pub async fn trace_transaction( - rpc_client: &dyn Rpc, - evm_loader: Pubkey, - tx: TxParams, - token: Pubkey, - chain_id: u64, - steps: u64, - commitment: CommitmentConfig, - accounts: &[Address], - solana_accounts: &[Pubkey], - trace_call_config: TraceCallConfig, -) -> Result { - let mut tracer = Tracer::new(trace_call_config.trace_config.enable_return_data); - - let (emulation_result, _storage) = evm_loader::evm::tracing::using(&mut tracer, || async { - emulate_transaction( - rpc_client, - evm_loader, - tx, - token, - chain_id, - steps, - commitment, - accounts, - solana_accounts, - trace_call_config, - ) - .await - }) - .await?; - - let (vm_trace, full_trace_data) = tracer.into_traces(); - - Ok(TracedCall { - vm_trace, - full_trace_data, - used_gas: emulation_result.used_gas, - result: emulation_result.result, - exit_status: emulation_result.exit_status, - }) -} - -#[derive(Serialize, Deserialize)] -pub struct TraceBlockReturn(pub Vec); +use crate::commands::emulate::EmulateResponse; +use log::info; +use serde_json::Value; +use solana_sdk::pubkey::Pubkey; -impl Display for TraceBlockReturn { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{{ traced call(s): {} }}", self.0.len()) - } -} - -#[allow(clippy::too_many_arguments)] -pub async fn trace_block( - rpc_client: &dyn Rpc, - evm_loader: Pubkey, - transactions: Vec, - token: Pubkey, - chain_id: u64, - steps: u64, - commitment: CommitmentConfig, - accounts: &[Address], - solana_accounts: &[Pubkey], - trace_config: &TraceConfig, -) -> Result { - setup_syscall_stubs(rpc_client).await?; - - let storage = EmulatorAccountStorage::with_accounts( - rpc_client, - evm_loader, - token, - chain_id, - commitment, - accounts, - solana_accounts, - &None, - None, - ) - .await?; +use crate::commands::get_config::BuildConfigSimulator; +use crate::errors::NeonError; +use crate::rpc::Rpc; +use crate::tracing::tracers::new_tracer; +use crate::types::EmulateRequest; - let mut results = vec![]; - for tx_params in transactions { - let result = trace_trx(tx_params, &storage, chain_id, steps, trace_config).await?; - results.push(result); - } - - Ok(TraceBlockReturn(results)) -} +pub async fn trace_transaction( + rpc: &(impl Rpc + BuildConfigSimulator), + program_id: Pubkey, + emulate_request: EmulateRequest, +) -> Result<(EmulateResponse, Option), NeonError> { + let trace_config = emulate_request + .trace_config + .as_ref() + .map(|c| c.trace_config.clone()) + .unwrap_or_default(); -async fn trace_trx<'a>( - tx_params: TxParams, - storage: &'a EmulatorAccountStorage<'a>, - chain_id: u64, - steps: u64, - trace_config: &TraceConfig, -) -> Result { - let mut tracer = Tracer::new(trace_config.enable_return_data); + let tracer = new_tracer(&emulate_request.tx, trace_config)?; - let emulation_result = evm_loader::evm::tracing::using(&mut tracer, || { - emulate_trx(tx_params, storage, chain_id, steps) - }) - .await?; + let response = super::emulate::execute(rpc, program_id, emulate_request, Some(tracer)).await?; - let (vm_trace, full_trace_data) = tracer.into_traces(); + info!("response: {:?}", response); - Ok(TracedCall { - vm_trace, - full_trace_data, - used_gas: emulation_result.used_gas, - result: emulation_result.result, - exit_status: emulation_result.exit_status, - }) + Ok(response) } diff --git a/evm_loader/lib/src/commands/transaction_executor.rs b/evm_loader/lib/src/commands/transaction_executor.rs index f195f9f71..75f190f95 100644 --- a/evm_loader/lib/src/commands/transaction_executor.rs +++ b/evm_loader/lib/src/commands/transaction_executor.rs @@ -1,10 +1,11 @@ -use std::{future::Future, sync::Arc}; +use std::cell::RefCell; +use std::future::Future; use serde::{Deserialize, Serialize}; -use tokio::sync::RwLock; +use solana_client::nonblocking::rpc_client::RpcClient; use { - crate::{errors::NeonError, rpc}, + crate::errors::NeonError, log::{debug, error, info, warn}, solana_sdk::{ account::Account, @@ -20,6 +21,7 @@ use { }; #[derive(Default, Debug, Serialize, Deserialize)] +#[allow(clippy::struct_field_names)] pub struct Stats { pub total_objects: u32, pub corrected_objects: u32, @@ -45,21 +47,21 @@ impl Stats { self.created_objects += 1; } } -pub struct TransactionExecutor { - pub client: Arc, +pub struct TransactionExecutor<'a, 'b> { + pub client: &'a RpcClient, pub send_trx: bool, - pub signatures: RwLock>, - pub stats: RwLock, - pub fee_payer: Arc, + pub signatures: RefCell>, + pub stats: RefCell, + pub fee_payer: &'b dyn Signer, } -impl TransactionExecutor { - pub fn new(client: Arc, fee_payer: Arc, send_trx: bool) -> Self { +impl<'a, 'b> TransactionExecutor<'a, 'b> { + pub fn new(client: &'a RpcClient, fee_payer: &'b dyn Signer, send_trx: bool) -> Self { Self { client, send_trx, - signatures: RwLock::new(vec![]), - stats: RwLock::new(Stats::default()), + signatures: RefCell::new(vec![]), + stats: RefCell::new(Stats::default()), fee_payer, } } @@ -96,9 +98,10 @@ impl TransactionExecutor { } } + #[allow(clippy::await_holding_refcell_ref)] pub async fn checkpoint(&self, commitment: CommitmentConfig) -> Result<(), NeonError> { let recent_blockhash = self.client.get_latest_blockhash().await?; - for sig in self.signatures.read().await.iter() { + for sig in self.signatures.borrow().iter() { self.client .confirm_transaction_with_spinner(sig, &recent_blockhash, commitment) .await?; @@ -115,7 +118,7 @@ impl TransactionExecutor { Transaction::new_with_payer(instructions, Some(&self.fee_payer.pubkey())); let blockhash = self.client.get_latest_blockhash().await?; - transaction.try_partial_sign(&[self.fee_payer.as_ref()], blockhash)?; + transaction.try_partial_sign(&[self.fee_payer], blockhash)?; transaction.try_sign(signing_keypairs, blockhash)?; Ok(transaction) @@ -161,7 +164,7 @@ impl TransactionExecutor { match verify(data.clone()).await { Ok(None) => { info!("{}: correct", name); - self.stats.write().await.inc_corrected_objects(); + self.stats.borrow_mut().inc_corrected_objects(); } Ok(Some(transaction)) => { if self.send_trx { @@ -169,24 +172,24 @@ impl TransactionExecutor { match result { Ok(signature) => { warn!("{}: updated in trx {}", name, signature); - self.signatures.write().await.push(signature); - self.stats.write().await.inc_modified_objects(); + self.signatures.borrow_mut().push(signature); + self.stats.borrow_mut().inc_modified_objects(); return Ok(Some(signature)); } Err(error) => { error!("{}: failed update with {}", name, error); - self.stats.write().await.inc_invalid_objects(); + self.stats.borrow_mut().inc_invalid_objects(); return Err(error); } }; }; debug!("{}: {:?}", name, transaction); - self.stats.write().await.inc_invalid_objects(); + self.stats.borrow_mut().inc_invalid_objects(); warn!("{}: will be updated", name); } Err(error) => { error!("{}: wrong object {:?}", name, error); - self.stats.write().await.inc_invalid_objects(); + self.stats.borrow_mut().inc_invalid_objects(); if self.send_trx { return Err(error); } @@ -196,7 +199,7 @@ impl TransactionExecutor { match create().await { Ok(None) => { info!("{}: missed ok", name); - self.stats.write().await.inc_corrected_objects(); + self.stats.borrow_mut().inc_corrected_objects(); } Ok(Some(transaction)) => { if self.send_trx { @@ -204,24 +207,24 @@ impl TransactionExecutor { match result { Ok(signature) => { warn!("{}: created in trx {}", name, signature); - self.signatures.write().await.push(signature); - self.stats.write().await.inc_created_objects(); + self.signatures.borrow_mut().push(signature); + self.stats.borrow_mut().inc_created_objects(); return Ok(Some(signature)); } Err(error) => { error!("{}: failed create with {}", name, error); - self.stats.write().await.inc_invalid_objects(); + self.stats.borrow_mut().inc_invalid_objects(); return Err(error); } }; }; debug!("{}: {:?}", name, transaction); warn!("{}: will be created", name); - self.stats.write().await.inc_created_objects(); + self.stats.borrow_mut().inc_created_objects(); } Err(error) => { error!("{}: can't be created: {:?}", name, error); - self.stats.write().await.inc_invalid_objects(); + self.stats.borrow_mut().inc_invalid_objects(); if self.send_trx { return Err(error); } diff --git a/evm_loader/lib/src/config.rs b/evm_loader/lib/src/config.rs index 0518dca68..1f648c222 100644 --- a/evm_loader/lib/src/config.rs +++ b/evm_loader/lib/src/config.rs @@ -1,118 +1,114 @@ -use std::{env, str::FromStr, sync::Arc}; - -use crate::{types::ChDbConfig, NeonError}; use serde::{Deserialize, Serialize}; -use solana_clap_utils::{ - input_validators::normalize_to_url_if_moniker, keypair::keypair_from_path, -}; -use solana_cli_config::Config as SolanaConfig; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; +use std::{env, str::FromStr}; + +const DEFAULT_ROCKSDB_PORT: u16 = 9888; #[derive(Debug)] pub struct Config { pub evm_loader: Pubkey, - pub fee_payer: Option>, + pub key_for_config: Pubkey, + pub fee_payer: Option, pub commitment: CommitmentConfig, pub solana_cli_config: solana_cli_config::Config, - pub db_config: Option, + pub db_config: Option, pub json_rpc_url: String, pub keypair_path: String, } -// impl Debug for Config { -// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// write!(f, "evm_loader={:?}", self.evm_loader) -// } -// } - -/// # Errors -pub fn create_from_api_comnfig(api_config: &APIOptions) -> Result { - let solana_cli_config: SolanaConfig = - if let Some(path) = api_config.solana_cli_config_path.clone() { - solana_cli_config::Config::load(path.as_str()).unwrap_or_default() - } else { - solana_cli_config::Config::default() - }; - - let commitment = CommitmentConfig::from_str(&api_config.commitment) - .unwrap_or_else(|_| CommitmentConfig::confirmed()); - - let json_rpc_url = normalize_to_url_if_moniker(api_config.json_rpc_url.clone()); - - let evm_loader: Pubkey = if let Ok(val) = Pubkey::from_str(&api_config.evm_loader) { - val - } else { - return Err(NeonError::EvmLoaderNotSpecified); - }; - - let keypair_path: String = api_config.keypair.clone(); - - let fee_payer = keypair_from_path( - &Default::default(), - &api_config.fee_payer, - "fee_payer", - true, - ) - .ok() - .map(Arc::new); - - let db_config: Option = Option::from(api_config.db_config.clone()); - - Ok(Config { - evm_loader, - fee_payer, - commitment, - solana_cli_config, - db_config, - json_rpc_url, - keypair_path, - }) -} - #[derive(Debug, Deserialize, Serialize)] pub struct APIOptions { pub solana_cli_config_path: Option, - pub commitment: String, - pub json_rpc_url: String, - pub evm_loader: String, - pub keypair: String, - pub fee_payer: String, - pub db_config: ChDbConfig, + pub commitment: CommitmentConfig, + pub solana_url: String, + pub solana_timeout: u64, + pub solana_max_retries: usize, + pub evm_loader: Pubkey, + pub key_for_config: Pubkey, + pub db_config: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum DbConfig { + RocksDbConfig(RocksDbConfig), + ChDbConfig(ChDbConfig), +} +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ChDbConfig { + pub clickhouse_url: Vec, + pub clickhouse_user: Option, + pub clickhouse_password: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RocksDbConfig { + pub rocksdb_host: String, + pub rocksdb_port: u16, } /// # Errors #[must_use] -pub fn load_api_config_from_enviroment() -> APIOptions { +pub fn load_api_config_from_environment() -> APIOptions { let solana_cli_config_path: Option = env::var("SOLANA_CLI_CONFIG_PATH").map(Some).unwrap_or(None); let commitment = env::var("COMMITMENT") .map(|v| v.to_lowercase()) - .expect("commitment variable must be set"); + .ok() + .and_then(|s| CommitmentConfig::from_str(&s).ok()) + .unwrap_or(CommitmentConfig::confirmed()); - let json_rpc_url = env::var("SOLANA_URL").expect("solana url variable must be set"); + let solana_url = env::var("SOLANA_URL").expect("solana url variable must be set"); - let evm_loader = env::var("EVM_LOADER").expect("evm loader variable must be set"); + let solana_timeout = env::var("SOLANA_TIMEOUT").unwrap_or_else(|_| "30".to_string()); + let solana_timeout = solana_timeout + .parse() + .expect("SOLANA_TIMEOUT variable must be a valid number"); - let keypair = env::var("KEYPAIR").expect("keypair must variable be set"); + let solana_max_retries = env::var("SOLANA_MAX_RETRIES").unwrap_or_else(|_| "10".to_string()); + let solana_max_retries = solana_max_retries + .parse() + .expect("SOLANA_MAX_RETRIES variable must be a valid number"); - let fee_payer = env::var("FEEPAIR").expect("fee pair variable must be set"); + let evm_loader = env::var("EVM_LOADER") + .ok() + .and_then(|v| Pubkey::from_str(&v).ok()) + .expect("EVM_LOADER variable must be a valid pubkey"); - let db_config = load_db_config_from_enviroment(); + let key_for_config = env::var("SOLANA_KEY_FOR_CONFIG") + .ok() + .and_then(|v| Pubkey::from_str(&v).ok()) + .expect("SOLANA_KEY_FOR_CONFIG variable must be a valid pubkey"); + + let db_config = load_db_config_from_environment(); APIOptions { solana_cli_config_path, commitment, - json_rpc_url, + solana_url, + solana_timeout, + solana_max_retries, evm_loader, - keypair, - fee_payer, + key_for_config, db_config, } } -/// # Errors -fn load_db_config_from_enviroment() -> ChDbConfig { +#[must_use] +pub fn load_db_config_from_environment() -> Option { + env::var("TRACER_DB_TYPE") + .ok() + .map(|var| match var.to_lowercase().as_str() { + "rocksdb" => Some(DbConfig::RocksDbConfig( + load_rocks_db_config_from_environment(), + )), + "clickhouse" => Some(DbConfig::ChDbConfig(load_ch_db_config_from_environment())), + "none" => None, + _ => panic!("TRACER_DB_TYPE env var must be either 'clickhouse', 'rocksdb', or 'none'"), + })? +} + +pub fn load_ch_db_config_from_environment() -> ChDbConfig { let clickhouse_url = env::var("NEON_DB_CLICKHOUSE_URLS") .map(|urls| { urls.split(';') @@ -129,29 +125,28 @@ fn load_db_config_from_enviroment() -> ChDbConfig { .map(Some) .unwrap_or(None); - let indexer_host = - env::var("NEON_DB_INDEXER_HOST").expect("neon db indexer host valiable must be set"); - - let indexer_port = - env::var("NEON_DB_INDEXER_PORT").expect("neon db indexer port valiable must be set"); - - let indexer_database = env::var("NEON_DB_INDEXER_DATABASE") - .expect("neon db indexer database valiable must be set"); - - let indexer_user = - env::var("NEON_DB_INDEXER_USER").expect("neon db indexer user valiable must be set"); - - let indexer_password = env::var("NEON_DB_INDEXER_PASSWORD") - .expect("neon db indexer password valiable must be set"); - ChDbConfig { clickhouse_url, clickhouse_user, clickhouse_password, - indexer_host, - indexer_port, - indexer_database, - indexer_user, - indexer_password, + } +} + +pub fn load_rocks_db_config_from_environment() -> RocksDbConfig { + let rocksdb_host = env::var("ROCKSDB_HOST") + .as_deref() + .unwrap_or("127.0.0.1") + .to_owned(); + + let rocksdb_port: u16 = env::var("ROCKSDB_PORT") + .ok() + .and_then(|port| port.parse::().ok()) + .unwrap_or(DEFAULT_ROCKSDB_PORT); + + tracing::info!("rocksdb host {rocksdb_host}, port {rocksdb_port}"); + + RocksDbConfig { + rocksdb_host, + rocksdb_port, } } diff --git a/evm_loader/lib/src/context.rs b/evm_loader/lib/src/context.rs deleted file mode 100644 index d9ff8f729..000000000 --- a/evm_loader/lib/src/context.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::sync::Arc; - -use crate::{ - rpc::CallDbClient, - rpc::{self, TrxDbClient}, - Config, NeonError, -}; -use hex::FromHex; -use solana_clap_utils::keypair::signer_from_path; -use solana_client::nonblocking::rpc_client::RpcClient; -use solana_sdk::signature::Signer; - -/// # Errors -pub async fn build_hash_rpc_client( - config: &Config, - hash: &str, -) -> Result, NeonError> { - let hash = <[u8; 32]>::from_hex(truncate_0x(hash))?; - - Ok(Arc::new( - TrxDbClient::new( - config.db_config.as_ref().expect("db-config not found"), - hash, - ) - .await, - )) -} - -pub fn truncate_0x(in_str: &str) -> &str { - if &in_str[..2] == "0x" { - &in_str[2..] - } else { - in_str - } -} - -pub struct Context { - pub rpc_client: Arc, - signer_config: Arc, -} - -impl Context { - pub fn signer(&self) -> Result, NeonError> { - build_signer(&self.signer_config) - } -} - -#[must_use] -pub fn create(rpc_client: Arc, signer_config: Arc) -> Context { - Context { - rpc_client, - signer_config, - } -} - -/// # Errors -pub fn build_signer(config: &Config) -> Result, NeonError> { - let mut wallet_manager = None; - - let signer = signer_from_path( - &Default::default(), - &config.keypair_path, - "keypair", - &mut wallet_manager, - ) - .map_err(|_| NeonError::KeypairNotSpecified)?; - - Ok(signer) -} - -/// # Errors -pub fn build_rpc_client( - config: &Config, - slot: Option, -) -> Result, NeonError> { - if let Some(slot) = slot { - return build_call_db_client(config, slot); - } - - Ok(Arc::new(RpcClient::new_with_commitment( - config.json_rpc_url.clone(), - config.commitment, - ))) -} - -/// # Errors -pub fn build_call_db_client(config: &Config, slot: u64) -> Result, NeonError> { - let config = config - .db_config - .clone() - .ok_or(NeonError::InvalidChDbConfig)?; - Ok(Arc::new(CallDbClient::new(&config, slot))) -} diff --git a/evm_loader/lib/src/errors.rs b/evm_loader/lib/src/errors.rs index 0ba461db0..996db22ea 100644 --- a/evm_loader/lib/src/errors.rs +++ b/evm_loader/lib/src/errors.rs @@ -1,19 +1,23 @@ //! Error types #![allow(clippy::use_self)] +use std::array::TryFromSliceError; use std::net::AddrParseError; +use std::string::FromUtf8Error; use log::error; +use neon_lib_interface::NeonEVMLibLoadError; use solana_cli::cli::CliError as SolanaCliError; use solana_client::client_error::ClientError as SolanaClientError; use solana_client::tpu_client::TpuSenderError as SolanaTpuSenderError; use solana_sdk::program_error::ProgramError as SolanaProgramError; use solana_sdk::pubkey::{Pubkey, PubkeyError as SolanaPubkeyError}; use solana_sdk::signer::SignerError as SolanaSignerError; +use solana_sdk::transaction::TransactionError; use thiserror::Error; use crate::commands::init_environment::EnvironmentError; -use crate::types::PgError; +use crate::types::tracer_ch_common::ChError; /// Errors that may be returned by the neon-cli program. #[derive(Debug, Error)] @@ -42,6 +46,8 @@ pub enum NeonError { /// EVM Loader Error #[error("EVM Error. {0}")] EvmError(#[from] evm_loader::error::Error), + #[error("Can't load db config")] + LoadingDBConfigError, /// Need specify evm_loader #[error("EVM loader must be specified.")] EvmLoaderNotSpecified, @@ -67,8 +73,7 @@ pub enum NeonError { InvalidAssociatedPda(Pubkey, Pubkey), #[error("")] InvalidChDbConfig, - /// too many steps - #[error("Too many steps")] + #[error("Out of Gas. Exceeded maximum number of EVM opcodes")] TooManySteps, #[error("Incorrect address {0:?}.")] IncorrectAddress(String), @@ -78,10 +83,6 @@ pub enum NeonError { TxParametersParsingError(String), #[error("AddrParseError. {0:?}")] AddrParseError(#[from] AddrParseError), - #[error("AxumError. {0:?}")] - AxumError(#[from] axum::Error), - #[error("SolanaClientError. {0:?}")] - SolanaClientError(solana_client::client_error::ClientError), /// Environment Error #[error("Environment error {0:?}")] EnvironmentError(#[from] EnvironmentError), @@ -93,14 +94,38 @@ pub enum NeonError { WrongEnvironment, #[error("Hex Error. {0}")] FromHexError(#[from] hex::FromHexError), - #[error("PostgreSQL Error: {0}")] - PostgreError(#[from] PgError), #[error("Panic: {0}")] Panic(String), + #[error("ClickHouse: {0}")] + ClickHouse(ChError), + #[error("RocksDbError {0}")] + RocksDb(anyhow::Error), + #[error("Slot {0} is less than earliest_rooted_slot={1}")] + EarlySlot(u64, u64), + #[error("Json Error. {0}")] + SerdeJson(#[from] serde_json::Error), + #[error("Transaction Error. {0}")] + TransactionError(#[from] TransactionError), + #[error("Bincode Error. {0}")] + BincodeError(#[from] bincode::Error), + #[error("FromUtf8 Error. {0}")] + FromUtf8Error(#[from] FromUtf8Error), + #[error("TryFromSlice Error. {0}")] + TryFromSliceError(#[from] TryFromSliceError), + #[error("Solana pubkey for config must be specified.")] + SolanaKeyForConfigNotSpecified, + #[error("library interface error")] + NeonEVMLibLoadError(#[from] NeonEVMLibLoadError), + #[error("Incorrect lib method")] + IncorrectLibMethod, + #[error("strum parse error {0:?}")] + StrumParseError(#[from] strum::ParseError), + #[error("Solana Simulator error {0:?}")] + SolanaSimulatorError(#[from] crate::solana_simulator::Error), } impl NeonError { - pub fn error_code(&self) -> i32 { + pub const fn error_code(&self) -> u32 { match self { NeonError::IncompleteEnvironment => 50, NeonError::WrongEnvironment => 51, @@ -115,8 +140,6 @@ impl NeonError { NeonError::PubkeyError(_) => 116, NeonError::EvmError(_) => 117, NeonError::AddrParseError(_) => 118, - NeonError::AxumError(_) => 119, - NeonError::SolanaClientError(_) => 120, NeonError::EvmLoaderNotSpecified => 201, NeonError::KeypairNotSpecified => 202, NeonError::IncorrectProgram(_) => 203, @@ -131,7 +154,20 @@ impl NeonError { NeonError::IncorrectAddress(_) => 248, NeonError::IncorrectIndex(_) => 249, NeonError::TxParametersParsingError(_) => 250, - NeonError::PostgreError(_) => 251, + NeonError::ClickHouse(_) => 252, + NeonError::EarlySlot(_, _) => 253, + NeonError::SerdeJson(_) => 254, + NeonError::TransactionError(_) => 256, + NeonError::BincodeError(_) => 257, + NeonError::FromUtf8Error(_) => 258, + NeonError::TryFromSliceError(_) => 259, + NeonError::SolanaKeyForConfigNotSpecified => 260, + NeonError::NeonEVMLibLoadError(_) => 261, + NeonError::LoadingDBConfigError => 262, + NeonError::IncorrectLibMethod => 263, + NeonError::StrumParseError(_) => 264, + NeonError::SolanaSimulatorError(_) => 265, + NeonError::RocksDb(_) => 266, } } } diff --git a/evm_loader/lib/src/event_listener/listener_tracer.rs b/evm_loader/lib/src/event_listener/listener_tracer.rs deleted file mode 100644 index cdc59c44a..000000000 --- a/evm_loader/lib/src/event_listener/listener_tracer.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::tracer::Tracer; -use crate::types::trace::FullTraceData; - -pub trait ListenerTracer { - fn begin_step(&mut self, stack: Vec<[u8; 32]>, memory: Vec); - fn end_step(&mut self, return_data: Option>); -} - -impl ListenerTracer for Tracer { - fn begin_step(&mut self, stack: Vec<[u8; 32]>, memory: Vec) { - let storage = self - .data - .last() - .map(|d| d.storage.clone()) - .unwrap_or_default(); - - self.data.push(FullTraceData { - stack, - memory, - storage, - return_data: None, - }); - } - - fn end_step(&mut self, return_data: Option>) { - let data = self.data.last_mut().expect("data was pushed in begin_step"); - data.return_data = return_data; - if let Some((index, value)) = self.vm.step_diff().storage_access { - data.storage.insert(index, value); - } - } -} diff --git a/evm_loader/lib/src/event_listener/listener_vm_tracer.rs b/evm_loader/lib/src/event_listener/listener_vm_tracer.rs deleted file mode 100644 index d9fa14be6..000000000 --- a/evm_loader/lib/src/event_listener/listener_vm_tracer.rs +++ /dev/null @@ -1,69 +0,0 @@ -use super::vm_tracer::VmTracer; -use crate::types::trace::{MemoryDiff, StorageDiff, VMTracer}; -use ethnum::U256; -use evm_loader::evm::{Context, ExitStatus}; - -pub trait ListenerVmTracer { - fn begin_vm(&mut self, context: Context, code: Vec); - fn end_vm(&mut self, status: ExitStatus); - fn begin_step(&mut self, opcode: u8, pc: usize); - fn end_step(&mut self, gas_used: u64); - fn storage_access(&mut self, index: U256, value: [u8; 32]); - fn storage_set(&mut self, index: U256, value: [u8; 32]); - fn stack_push(&mut self, value: [u8; 32]); - fn memory_set(&mut self, offset: usize, data: Vec); -} - -impl ListenerVmTracer for VmTracer { - fn begin_vm(&mut self, _context: Context, code: Vec) { - self.push_step_diff(); - - self.tracer.prepare_subtrace(code); - } - - fn end_vm(&mut self, _status: ExitStatus) { - self.pop_step_diff(); - - self.tracer.done_subtrace(); - } - - fn begin_step(&mut self, opcode: u8, pc: usize) { - let diff = self.step_diff(); - diff.stack_push.clear(); - diff.memory_set = None; - diff.storage_set = None; - diff.storage_access = None; - - self.tracer.trace_prepare_execute(pc, opcode); - } - - fn end_step(&mut self, gas_used: u64) { - let gas_used = U256::from(gas_used); - let diff = self.step_diff().clone(); - - self.tracer - .trace_executed(gas_used, diff.stack_push, diff.memory_set, diff.storage_set); - } - - fn storage_access(&mut self, index: U256, value: [u8; 32]) { - self.step_diff().storage_access = Some((index, value)); - } - - fn storage_set(&mut self, index: U256, value: [u8; 32]) { - self.step_diff().storage_set = Some(StorageDiff { - location: index, - value, - }); - } - - fn stack_push(&mut self, value: [u8; 32]) { - self.step_diff().stack_push.push(value); - } - - fn memory_set(&mut self, offset: usize, data: Vec) { - self.step_diff().memory_set = Some(MemoryDiff { - offset, - data: data.into(), - }); - } -} diff --git a/evm_loader/lib/src/event_listener/mod.rs b/evm_loader/lib/src/event_listener/mod.rs deleted file mode 100644 index 91e53e222..000000000 --- a/evm_loader/lib/src/event_listener/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -mod listener_tracer; -mod listener_vm_tracer; -pub mod tracer; -mod vm_tracer; - -use evm_loader::evm::tracing::{Event, EventListener}; -use {listener_tracer::ListenerTracer, listener_vm_tracer::ListenerVmTracer, tracer::Tracer}; - -impl EventListener for Tracer { - fn enable_return_data(&self) -> bool { - self.enable_return_data - } - - fn event(&mut self, event: Event) { - match event { - Event::BeginVM { context, code } => { - self.vm.begin_vm(context, code); - } - Event::EndVM { status } => { - self.vm.end_vm(status); - } - Event::BeginStep { - opcode, - pc, - stack, - memory, - } => { - self.begin_step(stack, memory); - self.vm.begin_step(opcode, pc); - } - Event::EndStep { - gas_used, - return_data, - } => { - self.end_step(return_data); - self.vm.end_step(gas_used); - } - Event::StackPush { value } => { - self.vm.stack_push(value); - } - Event::MemorySet { offset, data } => { - self.vm.memory_set(offset, data); - } - Event::StorageSet { index, value } => { - self.vm.storage_set(index, value); - } - Event::StorageAccess { index, value } => { - self.vm.storage_access(index, value); - } - }; - } -} diff --git a/evm_loader/lib/src/event_listener/tracer.rs b/evm_loader/lib/src/event_listener/tracer.rs deleted file mode 100644 index b3397f265..000000000 --- a/evm_loader/lib/src/event_listener/tracer.rs +++ /dev/null @@ -1,23 +0,0 @@ -use super::vm_tracer::VmTracer; -use crate::types::trace::{FullTraceData, VMTrace, VMTracer}; - -pub struct Tracer { - pub vm: VmTracer, - pub data: Vec, - pub(crate) enable_return_data: bool, -} - -impl Tracer { - pub fn new(enable_return_data: bool) -> Self { - Tracer { - vm: VmTracer::init(), - data: vec![], - enable_return_data, - } - } - - pub fn into_traces(self) -> (Option, Vec) { - let vm = self.vm.tracer.drain(); - (vm, self.data) - } -} diff --git a/evm_loader/lib/src/event_listener/vm_tracer.rs b/evm_loader/lib/src/event_listener/vm_tracer.rs deleted file mode 100644 index 67a038e51..000000000 --- a/evm_loader/lib/src/event_listener/vm_tracer.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::types::trace::{ExecutiveVMTracer, MemoryDiff, StorageDiff}; -use ethnum::U256; - -#[derive(Debug, Default, Clone)] -pub struct StepDiff { - pub storage_access: Option<(U256, [u8; 32])>, - pub storage_set: Option, - pub memory_set: Option, - pub stack_push: Vec<[u8; 32]>, -} - -pub struct VmTracer { - pub tracer: ExecutiveVMTracer, - step_diff: Vec, -} - -impl VmTracer { - pub fn init() -> Self { - VmTracer { - tracer: ExecutiveVMTracer::toplevel(), - step_diff: Vec::new(), - } - } - - pub fn push_step_diff(&mut self) { - self.step_diff.push(StepDiff::default()); - } - - pub fn pop_step_diff(&mut self) { - self.step_diff.pop(); - } - - pub fn step_diff(&mut self) -> &mut StepDiff { - self.step_diff - .last_mut() - .expect("diff was pushed in begin_vm") - } -} diff --git a/evm_loader/lib/src/lib.rs b/evm_loader/lib/src/lib.rs index a2a1eee4b..29eadd71d 100644 --- a/evm_loader/lib/src/lib.rs +++ b/evm_loader/lib/src/lib.rs @@ -1,15 +1,61 @@ +#![deny(warnings)] +#![deny(clippy::all, clippy::pedantic, clippy::nursery)] +#![allow( + clippy::future_not_send, + clippy::missing_errors_doc, + clippy::missing_panics_doc, + clippy::too_many_lines, + clippy::module_name_repetitions +)] + +pub mod abi; +pub mod account_data; pub mod account_storage; +pub mod build_info; +pub mod build_info_common; pub mod commands; pub mod config; -pub mod context; pub mod errors; -pub mod event_listener; pub mod rpc; -pub mod syscall_stubs; + +pub mod solana_simulator; +pub mod tracing; pub mod types; +use abi::_MODULE_WM_; +use abi_stable::export_root_module; pub use config::Config; -pub use context::Context; pub use errors::NeonError; +use neon_lib_interface::NeonEVMLib_Ref; pub type NeonResult = Result; + +const MODULE: NeonEVMLib_Ref = NeonEVMLib_Ref(_MODULE_WM_.static_as_prefix()); + +#[export_root_module] +#[must_use] +pub const fn get_root_module() -> NeonEVMLib_Ref { + MODULE +} + +use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Display, EnumString, IntoStaticStr, AsRefStr)] +pub enum LibMethod { + #[strum(serialize = "emulate")] + Emulate, + #[strum(serialize = "get_storage_at")] + GetStorageAt, + #[strum(serialize = "config")] + GetConfig, + #[strum(serialize = "balance")] + GetBalance, + #[strum(serialize = "contract")] + GetContract, + #[strum(serialize = "holder")] + GetHolder, + #[strum(serialize = "trace")] + Trace, + #[strum(serialize = "simulate_solana")] + SimulateSolana, +} diff --git a/evm_loader/lib/src/rpc/db_call_client.rs b/evm_loader/lib/src/rpc/db_call_client.rs index 2dc75e27d..32e97b9d5 100644 --- a/evm_loader/lib/src/rpc/db_call_client.rs +++ b/evm_loader/lib/src/rpc/db_call_client.rs @@ -1,85 +1,59 @@ use super::{e, Rpc}; -use crate::types::{ChDbConfig, TracerDb, TxParams}; +use crate::types::{TracerDb, TracerDbTrait}; +use crate::NeonError; +use crate::NeonError::RocksDb; use async_trait::async_trait; +use log::debug; use solana_client::{ client_error::Result as ClientResult, client_error::{ClientError, ClientErrorKind}, - rpc_config::{RpcSendTransactionConfig, RpcTransactionConfig}, - rpc_response::{Response, RpcResponseContext, RpcResult}, }; use solana_sdk::{ account::Account, clock::{Slot, UnixTimestamp}, - commitment_config::CommitmentConfig, - hash::Hash, pubkey::Pubkey, - signature::Signature, - transaction::Transaction, }; -use solana_transaction_status::{ - EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, -}; -use std::any::Any; pub struct CallDbClient { - pub slot: u64, tracer_db: TracerDb, + slot: u64, + tx_index_in_block: Option, } impl CallDbClient { - pub fn new(config: &ChDbConfig, slot: u64) -> Self { - let db = TracerDb::new(config); - Self { - slot, - tracer_db: db, - } - } -} + pub async fn new( + tracer_db: TracerDb, + slot: u64, + tx_index_in_block: Option, + ) -> Result { + let earliest_rooted_slot = tracer_db + .get_earliest_rooted_slot() + .await + .map_err(RocksDb)?; -#[async_trait] -impl Rpc for CallDbClient { - fn commitment(&self) -> CommitmentConfig { - CommitmentConfig::default() - } + if slot < earliest_rooted_slot { + return Err(NeonError::EarlySlot(slot, earliest_rooted_slot)); + } - async fn confirm_transaction_with_spinner( - &self, - _signature: &Signature, - _recent_blockhash: &Hash, - _commitment_config: CommitmentConfig, - ) -> ClientResult<()> { - Err(e!( - "confirm_transaction_with_spinner() not implemented for db_call_client" - )) + Ok(Self { + tracer_db, + slot, + tx_index_in_block, + }) } - async fn get_account(&self, key: &Pubkey) -> ClientResult { + async fn get_account_at(&self, key: &Pubkey) -> ClientResult> { self.tracer_db - .get_account_at(key, self.slot) + .get_account_at(key, self.slot, self.tx_index_in_block) .await - .map_err(|e| e!("load account error", key, e))? - .ok_or_else(|| e!("account not found", key)) + .map_err(|e| e!("load account error", key, e)) } +} - async fn get_account_with_commitment( - &self, - key: &Pubkey, - _: CommitmentConfig, - ) -> RpcResult> { - let account = self - .tracer_db - .get_account_at(key, self.slot) - .await - .map_err(|e| e!("load account error", key, e))?; - - let context = RpcResponseContext { - slot: self.slot, - api_version: None, - }; - Ok(Response { - context, - value: account, - }) +#[async_trait(?Send)] +impl Rpc for CallDbClient { + async fn get_account(&self, key: &Pubkey) -> ClientResult> { + self.get_account_at(key).await } async fn get_multiple_accounts( @@ -88,24 +62,12 @@ impl Rpc for CallDbClient { ) -> ClientResult>> { let mut result = Vec::new(); for key in pubkeys { - let account = self - .tracer_db - .get_account_at(key, self.slot) - .await - .map_err(|e| e!("load account error", key, e))?; - result.push(account); + result.push(self.get_account_at(key).await?); } + debug!("get_multiple_accounts: pubkeys={pubkeys:?} result={result:?}"); Ok(result) } - async fn get_account_data(&self, key: &Pubkey) -> ClientResult> { - Ok(self.get_account(key).await?.data) - } - - async fn get_block(&self, _slot: Slot) -> ClientResult { - Err(e!("get_block() not implemented for db_call_client")) - } - async fn get_block_time(&self, slot: Slot) -> ClientResult { self.tracer_db .get_block_time(slot) @@ -113,87 +75,11 @@ impl Rpc for CallDbClient { .map_err(|e| e!("get_block_time error", slot, e)) } - async fn get_latest_blockhash(&self) -> ClientResult { - Err(e!( - "get_latest_blockhash() not implemented for db_call_client" - )) - } - - async fn get_minimum_balance_for_rent_exemption(&self, _data_len: usize) -> ClientResult { - Err(e!( - "get_minimum_balance_for_rent_exemption() not implemented for db_call_client" - )) - } - async fn get_slot(&self) -> ClientResult { Ok(self.slot) } - async fn get_signature_statuses( - &self, - _signatures: &[Signature], - ) -> RpcResult>> { - Err(e!( - "get_signature_statuses() not implemented for db_call_client" - )) - } - - async fn get_transaction_with_config( - &self, - _signature: &Signature, - _config: RpcTransactionConfig, - ) -> ClientResult { - Err(e!( - "get_transaction_with_config() not implemented for db_call_client" - )) - } - - async fn send_transaction(&self, _transaction: &Transaction) -> ClientResult { - Err(e!("send_transaction() not implemented for db_call_client")) - } - - async fn send_and_confirm_transaction_with_spinner( - &self, - _transaction: &Transaction, - ) -> ClientResult { - Err(e!( - "send_and_confirm_transaction_with_spinner() not implemented for db_call_client" - )) - } - - async fn send_and_confirm_transaction_with_spinner_and_commitment( - &self, - _transaction: &Transaction, - _commitment: CommitmentConfig, - ) -> ClientResult { - Err(e!("send_and_confirm_transaction_with_spinner_and_commitment() not implemented for db_call_client")) - } - - async fn send_and_confirm_transaction_with_spinner_and_config( - &self, - _transaction: &Transaction, - _commitment: CommitmentConfig, - _config: RpcSendTransactionConfig, - ) -> ClientResult { - Err(e!("send_and_confirm_transaction_with_spinner_and_config() not implemented for db_call_client")) - } - - async fn get_latest_blockhash_with_commitment( - &self, - _commitment: CommitmentConfig, - ) -> ClientResult<(Hash, u64)> { - Err(e!( - "get_latest_blockhash_with_commitment() not implemented for db_call_client" - )) - } - - async fn get_transaction_data(&self) -> ClientResult { - Err(e!( - "get_transaction_data() not implemented for db_call_client" - )) - } - - fn as_any(&self) -> &dyn Any { - self + async fn get_deactivated_solana_features(&self) -> ClientResult> { + Ok(vec![]) // TODO } } diff --git a/evm_loader/lib/src/rpc/db_trx_client.rs b/evm_loader/lib/src/rpc/db_trx_client.rs deleted file mode 100644 index 65f0a08f1..000000000 --- a/evm_loader/lib/src/rpc/db_trx_client.rs +++ /dev/null @@ -1,219 +0,0 @@ -use super::{e, Rpc}; -use crate::types::{ChDbConfig, IndexerDb, TracerDb, TxParams}; -use async_trait::async_trait; -use solana_client::{ - client_error::Result as ClientResult, - client_error::{ClientError, ClientErrorKind}, - rpc_config::{RpcSendTransactionConfig, RpcTransactionConfig}, - rpc_response::{Response, RpcResponseContext, RpcResult}, -}; -use solana_sdk::{ - account::Account, - clock::{Slot, UnixTimestamp}, - commitment_config::CommitmentConfig, - hash::Hash, - pubkey::Pubkey, - signature::Signature, - transaction::Transaction, -}; -use solana_transaction_status::{ - EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, -}; -use std::any::Any; - -pub struct TrxDbClient { - pub hash: [u8; 32], - sol_sig: [u8; 64], - tracer_db: TracerDb, - indexer_db: IndexerDb, -} - -impl TrxDbClient { - pub async fn new(config: &ChDbConfig, hash: [u8; 32]) -> Self { - let tracer_db = TracerDb::new(config); - let indexer_db = IndexerDb::new(config).await; - let sol_sig = indexer_db - .get_sol_sig(&hash) - .await - .unwrap_or_else(|_| panic!("get_sol_sig error, hash: 0x{}", hex::encode(hash))); - - Self { - hash, - sol_sig, - tracer_db, - indexer_db, - } - } -} - -#[async_trait] -impl Rpc for TrxDbClient { - fn commitment(&self) -> CommitmentConfig { - CommitmentConfig::default() - } - - async fn confirm_transaction_with_spinner( - &self, - _signature: &Signature, - _recent_blockhash: &Hash, - _commitment_config: CommitmentConfig, - ) -> ClientResult<()> { - Err(e!( - "confirm_transaction_with_spinner() not implemented for trx rpc client" - )) - } - - async fn get_account(&self, key: &Pubkey) -> ClientResult { - self.tracer_db - .get_account_by_sol_sig(key, &self.sol_sig) - .await - .map_err(|e| e!("load account error", key, e))? - .ok_or_else(|| e!("account not found", key)) - } - - async fn get_account_with_commitment( - &self, - key: &Pubkey, - _commitment: CommitmentConfig, - ) -> RpcResult> { - let account = self - .tracer_db - .get_account_by_sol_sig(key, &self.sol_sig) - .await - .map_err(|e| e!("load account error", key, e))?; - - let slot = self - .indexer_db - .get_slot(&self.hash) - .await - .map_err(|e| e!("get_slot error", e))?; - - let context = RpcResponseContext { - slot, - api_version: None, - }; - Ok(Response { - context, - value: account, - }) - } - - async fn get_multiple_accounts( - &self, - pubkeys: &[Pubkey], - ) -> ClientResult>> { - let mut result = Vec::new(); - for key in pubkeys { - let account = self - .tracer_db - .get_account_by_sol_sig(key, &self.sol_sig) - .await - .map_err(|e| e!("load account error", key, e))?; - result.push(account); - } - Ok(result) - } - - async fn get_account_data(&self, key: &Pubkey) -> ClientResult> { - Ok(self.get_account(key).await?.data) - } - - async fn get_block(&self, _slot: Slot) -> ClientResult { - Err(e!("get_block() not implemented for db_trx_client")) - } - - async fn get_block_time(&self, slot: Slot) -> ClientResult { - self.tracer_db - .get_block_time(slot) - .await - .map_err(|e| e!("get_block_time error", slot, e)) - } - - async fn get_latest_blockhash(&self) -> ClientResult { - Err(e!( - "get_latest_blockhash() not implemented for db_trx_client" - )) - } - - async fn get_minimum_balance_for_rent_exemption(&self, _data_len: usize) -> ClientResult { - Err(e!( - "get_minimum_balance_for_rent_exemption() not implemented for db_trx_client" - )) - } - - async fn get_slot(&self) -> ClientResult { - self.indexer_db - .get_slot(&self.hash) - .await - .map_err(|e| e!("get_slot error", e)) - } - - async fn get_signature_statuses( - &self, - _signatures: &[Signature], - ) -> RpcResult>> { - Err(e!( - "get_signature_statuses() not implemented for db_trx_client" - )) - } - - async fn get_transaction_with_config( - &self, - _signature: &Signature, - _config: RpcTransactionConfig, - ) -> ClientResult { - Err(e!( - "get_transaction_with_config() not implemented for db_trx_client" - )) - } - - async fn send_transaction(&self, _transaction: &Transaction) -> ClientResult { - Err(e!("send_transaction() not implemented for db_trx_client")) - } - - async fn send_and_confirm_transaction_with_spinner( - &self, - _transaction: &Transaction, - ) -> ClientResult { - Err(e!( - "send_and_confirm_transaction_with_spinner() not implemented for db_trx_client" - )) - } - - async fn send_and_confirm_transaction_with_spinner_and_commitment( - &self, - _transaction: &Transaction, - _commitment: CommitmentConfig, - ) -> ClientResult { - Err(e!("send_and_confirm_transaction_with_spinner_and_commitment() not implemented for db_trx_client")) - } - - async fn send_and_confirm_transaction_with_spinner_and_config( - &self, - _transaction: &Transaction, - _commitment: CommitmentConfig, - _config: RpcSendTransactionConfig, - ) -> ClientResult { - Err(e!("send_and_confirm_transaction_with_spinner_and_config() not implemented for db_trx_client")) - } - - async fn get_latest_blockhash_with_commitment( - &self, - _commitment: CommitmentConfig, - ) -> ClientResult<(Hash, u64)> { - Err(e!( - "get_latest_blockhash_with_commitment() not implemented for db_trx_client" - )) - } - - async fn get_transaction_data(&self) -> ClientResult { - self.indexer_db - .get_transaction_data(&self.hash) - .await - .map_err(|e| e!("load transaction error", self.hash, e)) - } - - fn as_any(&self) -> &dyn Any { - self - } -} diff --git a/evm_loader/lib/src/rpc/emulator_client.rs b/evm_loader/lib/src/rpc/emulator_client.rs new file mode 100644 index 000000000..5a63af7f6 --- /dev/null +++ b/evm_loader/lib/src/rpc/emulator_client.rs @@ -0,0 +1,90 @@ +use async_trait::async_trait; +use evm_loader::account_storage::AccountStorage; +use log::debug; +use solana_client::client_error::Result as ClientResult; +use solana_sdk::{ + account::Account, + clock::{Slot, UnixTimestamp}, + pubkey::Pubkey, +}; + +use crate::account_storage::{fake_operator, EmulatorAccountStorage}; + +use super::Rpc; + +#[async_trait(?Send)] +impl<'rpc, T: Rpc> Rpc for EmulatorAccountStorage<'rpc, T> { + async fn get_account(&self, key: &Pubkey) -> ClientResult> { + if *key == self.operator() { + return Ok(Some(fake_operator())); + } + + if let Some(account_data) = self.accounts_get(key) { + return Ok(Some(Account::from(&*account_data))); + } + + let account = self._get_account_from_rpc(*key).await?.cloned(); + Ok(account) + } + + async fn get_multiple_accounts( + &self, + pubkeys: &[Pubkey], + ) -> ClientResult>> { + debug!("get_multiple_accounts: pubkeys={:?}", pubkeys); + if pubkeys.is_empty() { + return Ok(Vec::new()); + } + + let mut accounts = vec![None; pubkeys.len()]; + + let mut exists = vec![true; pubkeys.len()]; + let mut missing_keys = Vec::with_capacity(pubkeys.len()); + + for (i, pubkey) in pubkeys.iter().enumerate() { + if pubkey == &self.operator() { + accounts[i] = Some(fake_operator()); + continue; + } + + if let Some(account_data) = self.accounts_get(pubkey) { + debug!("cached account pubkey={pubkey} account_data={account_data:?}"); + accounts[i] = Some(Account::from(&*account_data)); + continue; + } + + exists[i] = false; + missing_keys.push(*pubkey); + } + + let response = self._get_multiple_accounts_from_rpc(&missing_keys).await?; + + debug!("get_multiple_accounts: missing_keys={missing_keys:?} response={response:?}",); + + let mut j = 0_usize; + for i in 0..pubkeys.len() { + if exists[i] { + continue; + } + + assert_eq!(pubkeys[i], missing_keys[j]); + accounts[i] = response[j].cloned(); + + j += 1; + } + + Ok(accounts) + } + + async fn get_block_time(&self, _slot: Slot) -> ClientResult { + Ok(self.block_timestamp().as_i64()) + } + + async fn get_slot(&self) -> ClientResult { + Ok(self.block_number().as_u64()) + } + + async fn get_deactivated_solana_features(&self) -> ClientResult> { + self._get_deactivated_solana_features().await + } +} diff --git a/evm_loader/lib/src/rpc/mod.rs b/evm_loader/lib/src/rpc/mod.rs index 05bbe2404..afa09bdd4 100644 --- a/evm_loader/lib/src/rpc/mod.rs +++ b/evm_loader/lib/src/rpc/mod.rs @@ -1,90 +1,40 @@ mod db_call_client; -mod db_trx_client; +mod emulator_client; mod validator_client; pub use db_call_client::CallDbClient; -pub use db_trx_client::TrxDbClient; +pub use validator_client::CloneRpcClient; -use crate::types::TxParams; +use crate::commands::get_config::{BuildConfigSimulator, ConfigSimulator}; use crate::{NeonError, NeonResult}; use async_trait::async_trait; +use enum_dispatch::enum_dispatch; use solana_cli::cli::CliError; -use solana_client::{ - client_error::Result as ClientResult, - nonblocking::rpc_client::RpcClient, - rpc_config::{RpcSendTransactionConfig, RpcTransactionConfig}, - rpc_response::RpcResult, -}; +use solana_client::client_error::Result as ClientResult; use solana_sdk::message::Message; use solana_sdk::native_token::lamports_to_sol; use solana_sdk::{ account::Account, clock::{Slot, UnixTimestamp}, - commitment_config::CommitmentConfig, - hash::Hash, pubkey::Pubkey, - signature::Signature, - transaction::Transaction, -}; -use solana_transaction_status::{ - EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, }; -use std::any::Any; -#[async_trait] -pub trait Rpc: Send + Sync { - fn commitment(&self) -> CommitmentConfig; - async fn confirm_transaction_with_spinner( - &self, - signature: &Signature, - recent_blockhash: &Hash, - commitment_config: CommitmentConfig, - ) -> ClientResult<()>; - async fn get_account(&self, key: &Pubkey) -> ClientResult; - async fn get_account_with_commitment( - &self, - key: &Pubkey, - commitment: CommitmentConfig, - ) -> RpcResult>; +#[async_trait(?Send)] +#[enum_dispatch] +pub trait Rpc { + async fn get_account(&self, key: &Pubkey) -> ClientResult>; async fn get_multiple_accounts(&self, pubkeys: &[Pubkey]) -> ClientResult>>; - async fn get_account_data(&self, key: &Pubkey) -> ClientResult>; - async fn get_block(&self, slot: Slot) -> ClientResult; async fn get_block_time(&self, slot: Slot) -> ClientResult; - async fn get_latest_blockhash(&self) -> ClientResult; - async fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> ClientResult; async fn get_slot(&self) -> ClientResult; - async fn get_signature_statuses( - &self, - signatures: &[Signature], - ) -> RpcResult>>; - async fn get_transaction_with_config( - &self, - signature: &Signature, - config: RpcTransactionConfig, - ) -> ClientResult; - async fn send_transaction(&self, transaction: &Transaction) -> ClientResult; - async fn send_and_confirm_transaction_with_spinner( - &self, - transaction: &Transaction, - ) -> ClientResult; - async fn send_and_confirm_transaction_with_spinner_and_commitment( - &self, - transaction: &Transaction, - commitment: CommitmentConfig, - ) -> ClientResult; - async fn send_and_confirm_transaction_with_spinner_and_config( - &self, - transaction: &Transaction, - commitment: CommitmentConfig, - config: RpcSendTransactionConfig, - ) -> ClientResult; - async fn get_latest_blockhash_with_commitment( - &self, - commitment: CommitmentConfig, - ) -> ClientResult<(Hash, u64)>; - async fn get_transaction_data(&self) -> ClientResult; - fn as_any(&self) -> &dyn Any; + + async fn get_deactivated_solana_features(&self) -> ClientResult>; +} + +#[enum_dispatch(BuildConfigSimulator, Rpc)] +pub enum RpcEnum { + CloneRpcClient, + CallDbClient, } macro_rules! e { @@ -101,10 +51,11 @@ macro_rules! e { ))) }; } + pub(crate) use e; pub(crate) async fn check_account_for_fee( - rpc_client: &RpcClient, + rpc_client: &CloneRpcClient, account_pubkey: &Pubkey, message: &Message, ) -> NeonResult<()> { diff --git a/evm_loader/lib/src/rpc/validator_client.rs b/evm_loader/lib/src/rpc/validator_client.rs index 73ded9193..1c64a8623 100644 --- a/evm_loader/lib/src/rpc/validator_client.rs +++ b/evm_loader/lib/src/rpc/validator_client.rs @@ -1,146 +1,225 @@ -use super::{e, Rpc}; -use crate::types::TxParams; +use crate::{config::APIOptions, Config}; + +use super::Rpc; use async_trait::async_trait; +use solana_account_decoder::{UiAccount, UiAccountEncoding}; use solana_client::{ - client_error::Result as ClientResult, - client_error::{ClientError, ClientErrorKind}, + client_error::{ClientError, ClientErrorKind, Result as ClientResult}, nonblocking::rpc_client::RpcClient, - rpc_config::{RpcSendTransactionConfig, RpcTransactionConfig}, - rpc_response::RpcResult, + rpc_config::RpcAccountInfoConfig, + rpc_request::RpcRequest, + rpc_response::Response, }; use solana_sdk::{ account::Account, clock::{Slot, UnixTimestamp}, - commitment_config::CommitmentConfig, - hash::Hash, pubkey::Pubkey, - signature::Signature, - transaction::Transaction, -}; -use solana_transaction_status::{ - EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, }; -use std::any::Any; +use std::{error::Error, ops::Deref, time::Duration}; +use std::{future::Future, sync::Arc}; +use tracing::debug; + +fn should_retry(e: &ClientError) -> bool { + let ClientErrorKind::Reqwest(reqwest_error) = e.kind() else { + return false; + }; -#[async_trait] -impl Rpc for RpcClient { - fn commitment(&self) -> CommitmentConfig { - self.commitment() + let Some(source) = reqwest_error.source() else { + return false; + }; + + let Some(hyper_error) = source.downcast_ref::() else { + return false; + }; + + if hyper_error.is_incomplete_message() { + return true; } - async fn confirm_transaction_with_spinner( - &self, - signature: &Signature, - recent_blockhash: &Hash, - commitment_config: CommitmentConfig, - ) -> ClientResult<()> { - self.confirm_transaction_with_spinner(signature, recent_blockhash, commitment_config) - .await + let Some(hyper_source) = hyper_error.source() else { + return false; + }; + + let Some(io_error) = hyper_source.downcast_ref::() else { + return false; + }; + + io_error.kind() == std::io::ErrorKind::ConnectionReset +} + +async fn with_retries(max_retries: usize, request: F) -> ClientResult +where + F: Fn() -> Fut, + Fut: Future>, +{ + for _ in 0..max_retries { + match request().await { + Ok(result) => return Ok(result), + Err(error) if should_retry(&error) => { + log::warn!("{}", error); + log::warn!("Retrying..."); + continue; + } + Err(error) => return Err(error), + } + } + + Err(ClientErrorKind::Custom("Max number of retries exceeded".to_string()).into()) +} + +#[derive(Clone)] +pub struct CloneRpcClient { + pub rpc: Arc, + pub key_for_config: Pubkey, + pub max_retries: usize, +} + +impl CloneRpcClient { + #[must_use] + pub fn new_from_config(config: &Config) -> Self { + let url = config.json_rpc_url.clone(); + let commitment = config.commitment; + + let rpc_client = RpcClient::new_with_commitment(url, commitment); + Self { + rpc: Arc::new(rpc_client), + key_for_config: config.key_for_config, + max_retries: 10, + } + } + + #[must_use] + pub fn new_from_api_config(config: &APIOptions) -> Self { + let url = config.solana_url.clone(); + let commitment = config.commitment; + let timeout = Duration::from_secs(config.solana_timeout); + + let rpc_client = RpcClient::new_with_timeout_and_commitment(url, timeout, commitment); + Self { + rpc: Arc::new(rpc_client), + key_for_config: config.key_for_config, + max_retries: config.solana_max_retries, + } } +} + +impl Deref for CloneRpcClient { + type Target = RpcClient; - async fn get_account(&self, key: &Pubkey) -> ClientResult { - self.get_account(key).await + fn deref(&self) -> &Self::Target { + &self.rpc } +} - async fn get_account_with_commitment( - &self, - key: &Pubkey, - commitment: CommitmentConfig, - ) -> RpcResult> { - self.get_account_with_commitment(key, commitment).await +#[async_trait(?Send)] +impl Rpc for CloneRpcClient { + async fn get_account(&self, key: &Pubkey) -> ClientResult> { + let request = || { + let config = RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64Zstd), + commitment: Some(self.commitment()), + data_slice: None, + min_context_slot: None, + }; + let params = serde_json::json!([key.to_string(), config]); + + self.send(RpcRequest::GetAccountInfo, params) + }; + + let response: serde_json::Value = with_retries(self.max_retries, request).await?; + let response: Response> = serde_json::from_value(response)?; + + let account = response.value.and_then(|v| v.decode()); + Ok(account) } async fn get_multiple_accounts( &self, pubkeys: &[Pubkey], ) -> ClientResult>> { - self.get_multiple_accounts(pubkeys).await - } - - async fn get_account_data(&self, key: &Pubkey) -> ClientResult> { - Ok(self.get_account(key).await?.data) - } - - async fn get_block(&self, slot: Slot) -> ClientResult { - self.get_block(slot).await + if pubkeys.is_empty() { + return Ok(Vec::new()); + } + + if pubkeys.len() == 1 { + let account = Rpc::get_account(self, &pubkeys[0]).await?; + debug!( + "get_multiple_accounts: single account pubkey={} account={:?}", + pubkeys[0], account + ); + return Ok(vec![account]); + } + + let mut result: Vec> = Vec::with_capacity(pubkeys.len()); + for chunk in pubkeys.chunks(100) { + let request = || self.rpc.get_multiple_accounts(chunk); + + let mut accounts = with_retries(self.max_retries, request).await?; + debug!( + "get_multiple_accounts: chunk pubkey={:?} account={:?}", + chunk, accounts + ); + result.append(&mut accounts); + } + + Ok(result) } async fn get_block_time(&self, slot: Slot) -> ClientResult { - self.get_block_time(slot).await + with_retries(self.max_retries, || self.rpc.get_block_time(slot)).await } - async fn get_latest_blockhash(&self) -> ClientResult { - self.get_latest_blockhash().await + async fn get_slot(&self) -> ClientResult { + with_retries(self.max_retries, || self.rpc.get_slot()).await } - async fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> ClientResult { - self.get_minimum_balance_for_rent_exemption(data_len).await - } + async fn get_deactivated_solana_features(&self) -> ClientResult> { + use std::time::{Duration, Instant}; + use tokio::sync::Mutex; - async fn get_slot(&self) -> ClientResult { - self.get_slot().await - } + struct Cache { + data: Vec, + timestamp: Instant, + } - async fn get_signature_statuses( - &self, - signatures: &[Signature], - ) -> RpcResult>> { - self.get_signature_statuses(signatures).await - } + static CACHE: Mutex> = Mutex::const_new(None); + let mut cache = CACHE.lock().await; - async fn get_transaction_with_config( - &self, - signature: &Signature, - config: RpcTransactionConfig, - ) -> ClientResult { - self.get_transaction_with_config(signature, config).await - } + if let Some(cache) = cache.as_ref() { + if cache.timestamp.elapsed() < Duration::from_secs(24 * 60 * 60) { + return Ok(cache.data.clone()); + } + } - async fn send_transaction(&self, transaction: &Transaction) -> ClientResult { - self.send_transaction(transaction).await - } + let feature_keys: Vec = solana_sdk::feature_set::FEATURE_NAMES + .keys() + .copied() + .collect(); - async fn send_and_confirm_transaction_with_spinner( - &self, - transaction: &Transaction, - ) -> ClientResult { - self.send_and_confirm_transaction_with_spinner(transaction) - .await - } + let features = Rpc::get_multiple_accounts(self, &feature_keys).await?; - async fn send_and_confirm_transaction_with_spinner_and_commitment( - &self, - transaction: &Transaction, - commitment: CommitmentConfig, - ) -> ClientResult { - self.send_and_confirm_transaction_with_spinner_and_commitment(transaction, commitment) - .await - } + let mut result = Vec::with_capacity(feature_keys.len()); + for (pubkey, feature) in feature_keys.iter().zip(features) { + let is_activated = feature + .and_then(|a| solana_sdk::feature::from_account(&a)) + .and_then(|f| f.activated_at) + .is_some(); - async fn send_and_confirm_transaction_with_spinner_and_config( - &self, - transaction: &Transaction, - commitment: CommitmentConfig, - config: RpcSendTransactionConfig, - ) -> ClientResult { - self.send_and_confirm_transaction_with_spinner_and_config(transaction, commitment, config) - .await - } + if !is_activated { + result.push(*pubkey); + } + } - async fn get_latest_blockhash_with_commitment( - &self, - commitment: CommitmentConfig, - ) -> ClientResult<(Hash, u64)> { - self.get_latest_blockhash_with_commitment(commitment).await - } + for feature in &result { + debug!("Deactivated feature: {}", feature); + } - async fn get_transaction_data(&self) -> ClientResult { - Err(e!( - "get_transaction_data() not implemented for validator_client" - )) - } + cache.replace(Cache { + data: result.clone(), + timestamp: Instant::now(), + }); + drop(cache); - fn as_any(&self) -> &dyn Any { - self + Ok(result) } } diff --git a/evm_loader/lib/src/solana_simulator/error.rs b/evm_loader/lib/src/solana_simulator/error.rs new file mode 100644 index 000000000..6bd4ac33e --- /dev/null +++ b/evm_loader/lib/src/solana_simulator/error.rs @@ -0,0 +1,23 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + // #[error("Unexpected response")] + // UnexpectedResponse, + #[error("Program Account error")] + ProgramAccountError, + #[error("Rpc Client error {0:?}")] + RpcClientError(#[from] solana_client::client_error::ClientError), + // #[error("IO error {0:?}")] + // IoError(#[from] std::io::Error), + #[error("Bincode error {0:?}")] + BincodeError(#[from] bincode::Error), + // #[error("TryFromIntError error {0:?}")] + // TryFromIntError(#[from] std::num::TryFromIntError), + #[error("Transaction error {0:?}")] + TransactionError(#[from] solana_sdk::transaction::TransactionError), + // #[error("Sanitize error {0:?}")] + // SanitizeError(#[from] solana_sdk::sanitize::SanitizeError), + #[error("Instruction error {0:?}")] + InstructionError(#[from] solana_sdk::instruction::InstructionError), + #[error("Invalid ALT")] + InvalidALT, +} diff --git a/evm_loader/lib/src/solana_simulator/mod.rs b/evm_loader/lib/src/solana_simulator/mod.rs new file mode 100644 index 000000000..96424943a --- /dev/null +++ b/evm_loader/lib/src/solana_simulator/mod.rs @@ -0,0 +1,684 @@ +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::Arc; + +pub use error::Error; +use evm_loader::solana_program::bpf_loader_upgradeable::UpgradeableLoaderState; +use evm_loader::solana_program::clock::Slot; +use evm_loader::solana_program::loader_v4; +use evm_loader::solana_program::loader_v4::{LoaderV4State, LoaderV4Status}; +use evm_loader::solana_program::message::SanitizedMessage; +use log::debug; +use solana_accounts_db::transaction_results::inner_instructions_list_from_instruction_trace; +use solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1; +use solana_loader_v4_program::create_program_runtime_environment_v2; +use solana_program_runtime::compute_budget::ComputeBudget; +use solana_program_runtime::loaded_programs::{ + LoadProgramMetrics, LoadedProgram, LoadedProgramType, LoadedProgramsForTxBatch, + ProgramRuntimeEnvironments, +}; +use solana_program_runtime::log_collector::LogCollector; +use solana_program_runtime::message_processor::MessageProcessor; +use solana_program_runtime::sysvar_cache::SysvarCache; +use solana_program_runtime::timings::ExecuteTimings; +use solana_runtime::accounts::construct_instructions_account; +use solana_runtime::builtins::BUILTINS; +use solana_runtime::{bank::TransactionSimulationResult, runtime_config::RuntimeConfig}; +use solana_sdk::account::{ + create_account_shared_data_with_fields, AccountSharedData, ReadableAccount, + DUMMY_INHERITABLE_ACCOUNT_FIELDS, PROGRAM_OWNERS, +}; +use solana_sdk::account_utils::StateMut; +use solana_sdk::address_lookup_table::error::AddressLookupError; +use solana_sdk::address_lookup_table::state::AddressLookupTable; +use solana_sdk::clock::Clock; +use solana_sdk::feature_set::FeatureSet; +use solana_sdk::fee_calculator::DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE; +use solana_sdk::message::v0::{LoadedAddresses, MessageAddressTableLookup}; +use solana_sdk::message::{AddressLoader, AddressLoaderError}; +use solana_sdk::rent::Rent; +use solana_sdk::transaction::TransactionError; +use solana_sdk::transaction_context::{ExecutionRecord, IndexOfAccount, TransactionContext}; +use solana_sdk::{ + account::Account, + address_lookup_table, bpf_loader_upgradeable, + hash::Hash, + pubkey::Pubkey, + signature::Keypair, + sysvar::{Sysvar, SysvarId}, + transaction::{SanitizedTransaction, VersionedTransaction}, +}; +pub use utils::SyncState; + +use crate::rpc::Rpc; + +mod error; +mod utils; + +pub struct SolanaSimulator { + runtime_config: RuntimeConfig, + feature_set: Arc, + accounts_db: HashMap, + sysvar_cache: SysvarCache, + payer: Keypair, +} + +impl SolanaSimulator { + pub async fn new(rpc: &impl Rpc) -> Result { + Self::new_with_config(rpc, RuntimeConfig::default(), SyncState::Yes).await + } + + pub async fn new_without_sync(rpc: &impl Rpc) -> Result { + Self::new_with_config(rpc, RuntimeConfig::default(), SyncState::No).await + } + + pub async fn new_with_config( + rpc: &impl Rpc, + runtime_config: RuntimeConfig, + sync_state: SyncState, + ) -> Result { + let mut feature_set = FeatureSet::all_enabled(); + + if sync_state == SyncState::Yes { + for feature in rpc.get_deactivated_solana_features().await? { + feature_set.deactivate(&feature); + } + } + + let mut sysvar_cache = SysvarCache::default(); + + sysvar_cache.set_rent(Rent::default()); + sysvar_cache.set_clock(Clock::default()); + + if sync_state == SyncState::Yes { + utils::sync_sysvar_accounts(rpc, &mut sysvar_cache).await?; + } + + Ok(Self { + runtime_config, + feature_set: Arc::new(feature_set), + accounts_db: HashMap::new(), + sysvar_cache, + payer: Keypair::new(), + }) + } + + pub async fn sync_accounts(&mut self, rpc: &impl Rpc, keys: &[Pubkey]) -> Result<(), Error> { + let mut storable_accounts: Vec<(&Pubkey, &Account)> = vec![]; + + let mut programdata_keys = vec![]; + + let mut accounts = rpc.get_multiple_accounts(keys).await?; + for (key, account) in keys.iter().zip(&mut accounts) { + let Some(account) = account else { + continue; + }; + + if account.executable && bpf_loader_upgradeable::check_id(&account.owner) { + let programdata_address = utils::program_data_address(account)?; + debug!( + "program_data_account: program={key} programdata=address{programdata_address}" + ); + programdata_keys.push(programdata_address); + } + + if account.owner == address_lookup_table::program::id() { + utils::reset_alt_slot(account).map_err(|_| Error::InvalidALT)?; + } + + storable_accounts.push((key, account)); + } + + let mut programdata_accounts = rpc.get_multiple_accounts(&programdata_keys).await?; + for (key, account) in programdata_keys.iter().zip(&mut programdata_accounts) { + let Some(account) = account else { + continue; + }; + + debug!("program_data_account: key={key} account={account:?}"); + utils::reset_program_data_slot(account)?; + storable_accounts.push((key, account)); + } + + self.set_multiple_accounts(&storable_accounts); + + Ok(()) + } + + #[must_use] + pub const fn payer(&self) -> &Keypair { + &self.payer + } + + #[must_use] + pub fn blockhash(&self) -> Hash { + Hash::new_unique() + } + + pub fn slot(&self) -> Result { + let clock = self.sysvar_cache.get_clock()?; + Ok(clock.slot) + } + + fn replace_sysvar_account(&mut self, sysvar: &S) + where + S: Sysvar + SysvarId, + { + let old_account = self.accounts_db.get(&S::id()); + let inherit = old_account.map_or(DUMMY_INHERITABLE_ACCOUNT_FIELDS, |a| { + (a.lamports(), a.rent_epoch()) + }); + + let account = create_account_shared_data_with_fields(sysvar, inherit); + self.accounts_db.insert(S::id(), account); + } + + pub fn set_clock(&mut self, clock: Clock) { + self.replace_sysvar_account(&clock); + self.sysvar_cache.set_clock(clock); + } + + pub fn set_multiple_accounts(&mut self, accounts: &[(&Pubkey, &Account)]) { + for (pubkey, account) in accounts { + self.accounts_db + .insert(**pubkey, AccountSharedData::from((*account).clone())); + } + } + + #[must_use] + pub fn get_shared_account(&self, pubkey: &Pubkey) -> Option { + self.accounts_db.get(pubkey).cloned() + } + + pub fn sanitize_transaction( + &self, + tx: VersionedTransaction, + verify: bool, + ) -> Result { + let sanitized_tx = { + let size = bincode::serialized_size(&tx)?; + if verify && (size > solana_sdk::packet::PACKET_DATA_SIZE as u64) { + return Err(TransactionError::SanitizeFailure.into()); + } + + let message_hash = if verify { + tx.verify_and_hash_message()? + } else { + tx.message.hash() + }; + + SanitizedTransaction::try_create(tx, message_hash, None, self) + }?; + + if verify { + sanitized_tx.verify_precompiles(&self.feature_set)?; + } + + Ok(sanitized_tx) + } + + pub fn process_transaction( + &self, + blockhash: Hash, + tx: &SanitizedTransaction, + ) -> Result { + let mut transaction_accounts = Vec::new(); + for key in tx.message().account_keys().iter() { + let account = if solana_sdk::sysvar::instructions::check_id(key) { + construct_instructions_account(tx.message()) + } else { + self.accounts_db.get(key).cloned().unwrap_or_default() + }; + transaction_accounts.push((*key, account)); + } + + let program_indices = Self::build_program_indices(tx, &mut transaction_accounts); + + let compute_budget = self.runtime_config.compute_budget.unwrap_or_default(); + let rent: Arc = self.sysvar_cache.get_rent()?; + let clock: Arc = self.sysvar_cache.get_clock()?; + + let lamports_before_tx = + transaction_accounts_lamports_sum(&transaction_accounts, tx.message()).unwrap_or(0); + + let mut transaction_context = TransactionContext::new( + transaction_accounts, + *rent, + compute_budget.max_invoke_stack_height, + compute_budget.max_instruction_trace_length, + ); + + let loaded_programs = self.load_programs(tx, &compute_budget, &clock); + + let mut modified_programs = LoadedProgramsForTxBatch::new( + clock.slot, + loaded_programs.environments.clone(), + loaded_programs.upcoming_environments.clone(), + loaded_programs.latest_root_epoch, + ); + + let log_collector = + LogCollector::new_ref_with_limit(self.runtime_config.log_messages_bytes_limit); + + let mut units_consumed = 0u64; + + let mut status = MessageProcessor::process_message( + tx.message(), + &program_indices, + &mut transaction_context, + Some(Rc::clone(&log_collector)), + &loaded_programs, + &mut modified_programs, + Arc::clone(&self.feature_set), + compute_budget, + &mut ExecuteTimings::default(), + &self.sysvar_cache, + blockhash, + DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2, + &mut units_consumed, + ); + + let inner_instructions = Some(inner_instructions_list_from_instruction_trace( + &transaction_context, + )); + + let ExecutionRecord { + accounts, + return_data, + touched_account_count: _touched_account_count, + accounts_resize_delta: _accounts_resize_delta, + } = transaction_context.into(); + + if status.is_ok() + && transaction_accounts_lamports_sum(&accounts, tx.message()) + .filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx) + .is_none() + { + status = Err(TransactionError::UnbalancedTransaction); + } + + let logs = Rc::try_unwrap(log_collector) + .map(|log_collector| log_collector.into_inner().into_messages()) + .ok() + .unwrap(); + + let return_data = if return_data.data.is_empty() { + None + } else { + Some(return_data) + }; + + Ok(TransactionSimulationResult { + result: status, + logs, + post_simulation_accounts: accounts, + units_consumed, + return_data, + inner_instructions, + }) + } + + #[allow(clippy::cast_possible_truncation)] + fn build_program_indices( + tx: &SanitizedTransaction, + transaction_accounts: &mut Vec<(Pubkey, AccountSharedData)>, + ) -> Vec> { + let builtins_start_index = transaction_accounts.len(); + tx.message() + .instructions() + .iter() + .map(|instruction| { + let mut account_indices: Vec = Vec::with_capacity(2); + + let program_index = instruction.program_id_index as usize; + let (program_id, program_account) = &transaction_accounts[program_index]; + + if solana_sdk::native_loader::check_id(program_id) { + return account_indices; + } + + account_indices.insert(0, program_index as IndexOfAccount); + + let owner = program_account.owner(); + if solana_sdk::native_loader::check_id(owner) { + return account_indices; + } + + if let Some(owner_index) = transaction_accounts[builtins_start_index..] + .iter() + .position(|(key, _)| key == owner) + { + let owner_index = owner_index + builtins_start_index; + account_indices.insert(0, owner_index as IndexOfAccount); + } else { + let _builtin = BUILTINS + .iter() + .find(|builtin| builtin.program_id == *owner) + .unwrap(); + + let owner_account = + AccountSharedData::new(100, 100, &solana_sdk::native_loader::id()); + transaction_accounts.push((*owner, owner_account)); + + let owner_index = transaction_accounts.len() - 1; + account_indices.insert(0, owner_index as IndexOfAccount); + } + + account_indices + }) + .collect() + } + + fn load_programs( + &self, + tx: &SanitizedTransaction, + compute_budget: &ComputeBudget, + clock: &Arc, + ) -> LoadedProgramsForTxBatch { + let program_runtime_environments = ProgramRuntimeEnvironments { + program_runtime_v1: Arc::new( + create_program_runtime_environment_v1( + &self.feature_set, + compute_budget, + true, + true, + ) + .unwrap(), + ), + program_runtime_v2: Arc::new(create_program_runtime_environment_v2( + compute_budget, + true, + )), + }; + + let mut loaded_programs = LoadedProgramsForTxBatch::new( + clock.slot, + program_runtime_environments.clone(), + None, + clock.epoch, + ); + + tx.message().account_keys().iter().for_each(|key| { + if loaded_programs.find(key).is_none() { + let account = self.accounts_db.get(key).cloned().unwrap_or_default(); + if PROGRAM_OWNERS.iter().any(|owner| account.owner() == owner) { + let mut load_program_metrics = LoadProgramMetrics { + program_id: key.to_string(), + ..LoadProgramMetrics::default() + }; + let loaded_program = match self.load_program_accounts(account) { + ProgramAccountLoadResult::InvalidAccountData => { + LoadedProgram::new_tombstone(0, LoadedProgramType::Closed) + } + + ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account) => { + LoadedProgram::new( + program_account.owner(), + program_runtime_environments.program_runtime_v1.clone(), + 0, + 0, + None, + program_account.data(), + program_account.data().len(), + &mut load_program_metrics, + ) + .unwrap() + } + + ProgramAccountLoadResult::ProgramOfLoaderV3( + program_account, + programdata_account, + _slot, + ) => { + let programdata = programdata_account + .data() + .get(UpgradeableLoaderState::size_of_programdata_metadata()..) + .unwrap(); + LoadedProgram::new( + program_account.owner(), + program_runtime_environments.program_runtime_v1.clone(), + 0, + 0, + None, + programdata, + program_account + .data() + .len() + .saturating_add(programdata_account.data().len()), + &mut load_program_metrics, + ) + .unwrap() + } + + ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, _slot) => { + let elf_bytes = program_account + .data() + .get(LoaderV4State::program_data_offset()..) + .unwrap(); + LoadedProgram::new( + program_account.owner(), + program_runtime_environments.program_runtime_v2.clone(), + 0, + 0, + None, + elf_bytes, + program_account.data().len(), + &mut load_program_metrics, + ) + .unwrap() + } + }; + loaded_programs.replenish(*key, Arc::new(loaded_program)); + } + } + }); + + for builtin in BUILTINS { + // create_loadable_account_with_fields + let program = LoadedProgram::new_builtin(0, builtin.name.len(), builtin.entrypoint); + loaded_programs.replenish(builtin.program_id, Arc::new(program)); + } + + loaded_programs + } + + pub fn simulate_legacy_transaction( + &self, + tx: solana_sdk::transaction::Transaction, + ) -> Result { + let versioned_transaction = VersionedTransaction::from(tx); + self.process_transaction( + *versioned_transaction.message.recent_blockhash(), + &self.sanitize_transaction(versioned_transaction, false)?, + ) + } + + fn load_program_accounts( + &self, + program_account: AccountSharedData, + ) -> ProgramAccountLoadResult { + debug_assert!(solana_bpf_loader_program::check_loader_id( + program_account.owner() + )); + + if loader_v4::check_id(program_account.owner()) { + return solana_loader_v4_program::get_state(program_account.data()) + .ok() + .and_then(|state| { + (!matches!(state.status, LoaderV4Status::Retracted)).then_some(state.slot) + }) + .map_or(ProgramAccountLoadResult::InvalidAccountData, |slot| { + ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot) + }); + } + + if !bpf_loader_upgradeable::check_id(program_account.owner()) { + return ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account); + } + + if let Ok(UpgradeableLoaderState::Program { + programdata_address, + }) = program_account.state() + { + if let Some(programdata_account) = self.accounts_db.get(&programdata_address).cloned() { + if let Ok(UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: _, + }) = programdata_account.state() + { + return ProgramAccountLoadResult::ProgramOfLoaderV3( + program_account, + programdata_account, + slot, + ); + } + } + } + ProgramAccountLoadResult::InvalidAccountData + } +} + +enum ProgramAccountLoadResult { + InvalidAccountData, + ProgramOfLoaderV1orV2(AccountSharedData), + ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot), + ProgramOfLoaderV4(AccountSharedData, Slot), +} + +fn transaction_accounts_lamports_sum( + accounts: &[(Pubkey, AccountSharedData)], + message: &SanitizedMessage, +) -> Option { + let mut lamports_sum = 0u128; + for i in 0..message.account_keys().len() { + let (_, account) = accounts.get(i)?; + lamports_sum = lamports_sum.checked_add(u128::from(account.lamports()))?; + } + Some(lamports_sum) +} + +impl AddressLoader for &SolanaSimulator { + fn load_addresses( + self, + lookups: &[MessageAddressTableLookup], + ) -> Result { + let loaded_addresses = lookups + .iter() + .map(|address_table_lookup| { + let table_account = self + .get_shared_account(&address_table_lookup.account_key) + .ok_or(AddressLookupError::LookupTableAccountNotFound)?; + + if table_account.owner() != &address_lookup_table::program::id() { + return Err(AddressLookupError::InvalidAccountOwner); + } + + let current_slot = self + .slot() + .map_err(|_| AddressLookupError::LookupTableAccountNotFound)?; + + let slot_hashes = self + .sysvar_cache + .get_slot_hashes() + .map_err(|_| AddressLookupError::LookupTableAccountNotFound)?; + + let lookup_table = AddressLookupTable::deserialize(table_account.data()) + .map_err(|_| AddressLookupError::InvalidAccountData)?; + + Ok(LoadedAddresses { + writable: lookup_table.lookup( + current_slot, + &address_table_lookup.writable_indexes, + &slot_hashes, + )?, + readonly: lookup_table.lookup( + current_slot, + &address_table_lookup.readonly_indexes, + &slot_hashes, + )?, + }) + }) + .collect::>()?; + + Ok(loaded_addresses) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_hex_encode_tx() { + let bytes = [ + 1, 58, 23, 68, 33, 87, 114, 44, 125, 7, 236, 250, 189, 152, 80, 109, 13, 162, 107, 101, + 124, 216, 66, 80, 213, 40, 53, 51, 182, 30, 255, 233, 81, 173, 129, 169, 64, 34, 99, + 244, 26, 97, 234, 36, 224, 159, 246, 251, 59, 49, 38, 37, 93, 186, 243, 244, 21, 130, + 128, 72, 105, 242, 160, 60, 7, 1, 0, 12, 17, 231, 21, 48, 207, 152, 236, 233, 187, 223, + 100, 82, 93, 7, 113, 26, 194, 124, 70, 245, 140, 6, 215, 63, 170, 178, 46, 130, 201, + 93, 40, 215, 178, 87, 173, 47, 151, 71, 81, 72, 73, 80, 156, 165, 181, 37, 178, 136, + 180, 35, 74, 234, 62, 83, 114, 123, 149, 139, 225, 217, 56, 147, 131, 206, 45, 105, + 208, 134, 80, 73, 140, 123, 117, 252, 233, 38, 133, 137, 99, 77, 55, 167, 149, 85, 24, + 36, 45, 148, 20, 106, 29, 21, 63, 99, 229, 104, 209, 135, 106, 102, 83, 227, 22, 170, + 173, 106, 135, 14, 233, 81, 75, 157, 216, 252, 53, 197, 36, 130, 119, 213, 126, 95, 12, + 254, 107, 149, 132, 193, 66, 216, 86, 105, 62, 222, 21, 157, 45, 17, 10, 178, 124, 189, + 91, 143, 219, 219, 104, 133, 196, 14, 129, 66, 215, 247, 28, 36, 71, 221, 69, 99, 94, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, + 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0, 7, 215, 243, 216, 48, + 133, 75, 3, 197, 149, 83, 168, 150, 114, 90, 186, 70, 253, 177, 48, 225, 211, 142, 191, + 241, 13, 77, 252, 188, 202, 215, 228, 40, 226, 52, 125, 171, 215, 127, 195, 1, 133, 3, + 55, 38, 152, 124, 161, 200, 77, 29, 38, 158, 98, 147, 173, 205, 87, 50, 86, 244, 38, + 253, 251, 60, 0, 57, 43, 120, 125, 56, 168, 83, 209, 36, 5, 118, 52, 196, 60, 113, 51, + 198, 18, 70, 29, 116, 254, 177, 127, 66, 72, 21, 82, 134, 192, 90, 72, 243, 77, 170, + 80, 23, 118, 170, 39, 23, 58, 58, 106, 22, 11, 26, 111, 175, 251, 186, 179, 49, 60, 52, + 157, 46, 126, 243, 174, 139, 107, 91, 173, 150, 43, 91, 46, 211, 20, 76, 212, 62, 196, + 7, 39, 240, 9, 4, 65, 17, 249, 203, 197, 140, 181, 38, 196, 213, 83, 115, 120, 145, 11, + 94, 103, 100, 49, 70, 40, 205, 74, 224, 203, 66, 252, 113, 38, 143, 168, 244, 184, 32, + 23, 8, 184, 93, 133, 24, 57, 107, 236, 123, 117, 13, 177, 115, 140, 234, 18, 14, 113, + 230, 247, 22, 144, 89, 29, 2, 140, 149, 150, 43, 254, 247, 11, 251, 18, 99, 69, 131, + 32, 152, 113, 83, 125, 6, 8, 196, 54, 180, 68, 255, 57, 153, 93, 12, 42, 185, 242, 64, + 126, 36, 91, 71, 227, 19, 94, 244, 179, 157, 76, 97, 249, 91, 53, 39, 250, 133, 28, + 197, 116, 21, 173, 61, 163, 236, 185, 166, 242, 81, 61, 9, 179, 63, 76, 24, 114, 82, + 18, 182, 138, 185, 121, 251, 101, 108, 251, 132, 89, 201, 201, 238, 34, 115, 103, 121, + 227, 23, 147, 27, 162, 42, 67, 149, 80, 211, 223, 101, 182, 167, 128, 216, 67, 155, 21, + 245, 228, 13, 178, 54, 172, 119, 171, 72, 106, 157, 37, 107, 127, 172, 209, 12, 110, + 173, 6, 145, 14, 187, 127, 145, 195, 53, 103, 119, 182, 129, 49, 170, 8, 237, 99, 87, + 32, 152, 64, 4, 6, 0, 9, 3, 2, 0, 0, 0, 0, 0, 0, 0, 6, 0, 5, 1, 0, 0, 4, 0, 6, 0, 5, 2, + 192, 92, 21, 0, 9, 15, 2, 0, 3, 4, 5, 7, 8, 1, 10, 11, 12, 13, 14, 15, 16, 122, 52, 14, + 0, 0, 0, 88, 49, 0, 0, 12, 0, 0, 0, 248, 107, 1, 132, 119, 53, 148, 0, 132, 9, 61, 92, + 128, 148, 163, 222, 235, 37, 106, 70, 13, 34, 201, 224, 71, 134, 58, 94, 231, 159, 237, + 188, 62, 73, 128, 132, 52, 103, 185, 80, 130, 1, 2, 160, 244, 180, 133, 103, 74, 93, + 21, 159, 64, 57, 219, 13, 44, 152, 135, 226, 169, 32, 159, 38, 122, 70, 119, 143, 47, + 187, 8, 70, 187, 84, 246, 50, 160, 37, 44, 189, 121, 39, 163, 64, 35, 212, 96, 114, + 159, 112, 19, 137, 242, 122, 153, 40, 66, 91, 58, 177, 212, 56, 211, 173, 170, 83, 13, + 176, 85, + ]; + eprintln!("{}", hex::encode(bytes)); + // assert_eq!(true, false); + } + + #[test] + fn test_hex_encode_v0_tx() { + let bytes = [ + 1, 195, 51, 167, 150, 247, 55, 53, 248, 87, 145, 226, 107, 103, 143, 215, 85, 62, 100, + 128, 254, 136, 249, 2, 68, 10, 226, 181, 117, 197, 116, 123, 10, 25, 188, 7, 130, 56, + 16, 164, 148, 238, 144, 40, 1, 29, 213, 36, 243, 153, 42, 247, 46, 74, 245, 246, 187, + 202, 223, 137, 176, 212, 204, 198, 6, 128, 1, 0, 3, 4, 129, 250, 211, 63, 136, 192, + 175, 63, 138, 71, 161, 34, 135, 75, 55, 3, 149, 61, 84, 23, 252, 239, 203, 80, 170, + 175, 222, 166, 136, 217, 173, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 6, 70, 111, 229, 33, 23, 50, 255, 236, + 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, + 64, 0, 0, 0, 60, 0, 57, 43, 120, 125, 56, 168, 83, 209, 36, 5, 118, 52, 196, 60, 113, + 51, 198, 18, 70, 29, 116, 254, 177, 127, 66, 72, 21, 82, 134, 192, 120, 25, 149, 73, + 158, 141, 39, 109, 12, 97, 90, 180, 212, 130, 149, 30, 62, 56, 197, 80, 116, 62, 58, 5, + 27, 10, 213, 206, 39, 141, 199, 83, 4, 2, 0, 9, 3, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 5, 1, + 0, 0, 4, 0, 2, 0, 5, 2, 192, 92, 21, 0, 3, 32, 24, 0, 29, 17, 1, 6, 28, 26, 33, 16, 18, + 31, 8, 12, 11, 25, 13, 32, 30, 15, 14, 4, 10, 22, 9, 27, 23, 21, 5, 19, 20, 7, 5, 51, + 29, 0, 0, 0, 1, 35, 227, 136, 54, 194, 46, 23, 104, 208, 155, 167, 222, 101, 18, 44, + 98, 220, 121, 194, 180, 176, 19, 46, 226, 47, 225, 188, 124, 123, 18, 197, 81, 26, 0, + 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 24, 25, 26, 27, + 28, 29, 4, 2, 3, 18, 23, + ]; + eprintln!("{}", hex::encode(bytes)); + // assert_eq!(true, false); + } +} diff --git a/evm_loader/lib/src/solana_simulator/utils.rs b/evm_loader/lib/src/solana_simulator/utils.rs new file mode 100644 index 000000000..d30dbefe2 --- /dev/null +++ b/evm_loader/lib/src/solana_simulator/utils.rs @@ -0,0 +1,154 @@ +use super::error::Error; +use log::debug; +use solana_program_runtime::sysvar_cache::SysvarCache; +use solana_sdk::{ + account::Account, + account_utils::StateMut, + address_lookup_table::{ + self, + state::{AddressLookupTable, LookupTableMeta}, + }, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + pubkey::Pubkey, + sysvar, +}; + +use crate::rpc::Rpc; + +#[derive(Eq, PartialEq, Copy, Clone)] +pub enum SyncState { + No, + Yes, +} + +pub async fn sync_sysvar_accounts( + rpc: &impl Rpc, + sysvar_cache: &mut SysvarCache, +) -> Result<(), Error> { + let keys = sysvar::ALL_IDS.clone(); + let accounts = rpc.get_multiple_accounts(&keys).await?; + for (key, account) in keys.into_iter().zip(accounts) { + let Some(account) = account else { + continue; + }; + + match key { + sysvar::clock::ID => { + use sysvar::clock::Clock; + + let clock: Clock = bincode::deserialize(&account.data)?; + sysvar_cache.set_clock(clock); + } + sysvar::epoch_rewards::ID => { + use sysvar::epoch_rewards::EpochRewards; + + let epoch_rewards: EpochRewards = bincode::deserialize(&account.data)?; + sysvar_cache.set_epoch_rewards(epoch_rewards); + } + sysvar::epoch_schedule::ID => { + use sysvar::epoch_schedule::EpochSchedule; + + let epoch_schedule: EpochSchedule = bincode::deserialize(&account.data)?; + sysvar_cache.set_epoch_schedule(epoch_schedule); + } + sysvar::rent::ID => { + use sysvar::rent::Rent; + + let rent: Rent = bincode::deserialize(&account.data)?; + sysvar_cache.set_rent(rent); + } + sysvar::slot_hashes::ID => { + use sysvar::slot_hashes::SlotHashes; + + let slot_hashes: SlotHashes = bincode::deserialize(&account.data)?; + sysvar_cache.set_slot_hashes(slot_hashes); + } + sysvar::stake_history::ID => { + use sysvar::stake_history::StakeHistory; + + let stake_history: StakeHistory = bincode::deserialize(&account.data)?; + sysvar_cache.set_stake_history(stake_history); + } + #[allow(deprecated)] + id if sysvar::fees::check_id(&id) => { + use sysvar::fees::Fees; + + let fees: Fees = bincode::deserialize(&account.data)?; + sysvar_cache.set_fees(fees); + } + sysvar::last_restart_slot::ID => { + use sysvar::last_restart_slot::LastRestartSlot; + + let last_restart_slot: LastRestartSlot = bincode::deserialize(&account.data)?; + sysvar_cache.set_last_restart_slot(last_restart_slot); + } + #[allow(deprecated)] + id if sysvar::recent_blockhashes::check_id(&id) => { + use sysvar::recent_blockhashes::RecentBlockhashes; + + let recent_blockhashes: RecentBlockhashes = bincode::deserialize(&account.data)?; + sysvar_cache.set_recent_blockhashes(recent_blockhashes); + } + _ => {} + } + } + + Ok(()) +} + +pub fn program_data_address(account: &Account) -> Result { + assert!(account.executable); + assert_eq!(account.owner, bpf_loader_upgradeable::id()); + + let UpgradeableLoaderState::Program { + programdata_address, + .. + } = account.state()? + else { + return Err(Error::ProgramAccountError); + }; + + Ok(programdata_address) +} + +pub fn reset_program_data_slot(account: &mut Account) -> Result<(), Error> { + assert_eq!(account.owner, bpf_loader_upgradeable::id()); + + let UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address, + } = account.state()? + else { + return Err(Error::ProgramAccountError); + }; + + debug!( + "slot_before_update: slot={slot} upgrade_authority_address={upgrade_authority_address:?}" + ); + + let new_state = UpgradeableLoaderState::ProgramData { + slot: 0, + upgrade_authority_address, + }; + account.set_state(&new_state)?; + + debug!( + "slot_after_update: slot={slot} upgrade_authority_address={upgrade_authority_address:?}" + ); + + Ok(()) +} + +pub fn reset_alt_slot(account: &mut Account) -> Result<(), Error> { + assert_eq!(account.owner, address_lookup_table::program::id()); + + let lookup_table = AddressLookupTable::deserialize(&account.data)?; + let metadata = LookupTableMeta { + last_extended_slot: 0, + ..lookup_table.meta + }; + + AddressLookupTable::overwrite_meta_data(&mut account.data, metadata)?; + + Ok(()) +} diff --git a/evm_loader/lib/src/syscall_stubs.rs b/evm_loader/lib/src/syscall_stubs.rs deleted file mode 100644 index 3a724fb3b..000000000 --- a/evm_loader/lib/src/syscall_stubs.rs +++ /dev/null @@ -1,48 +0,0 @@ -use log::info; -use solana_sdk::{program_error::ProgramError, program_stubs::SyscallStubs, sysvar::rent::Rent}; - -use crate::{errors::NeonError, rpc::Rpc}; - -pub struct Stubs { - rent: Rent, -} - -impl Stubs { - pub async fn new(rpc_client: &dyn Rpc) -> Result, NeonError> { - let rent_pubkey = solana_sdk::sysvar::rent::id(); - let data = rpc_client.get_account_data(&rent_pubkey).await?; - let rent = bincode::deserialize(&data).map_err(|_| ProgramError::InvalidArgument)?; - - Ok(Box::new(Self { rent })) - } -} - -impl SyscallStubs for Stubs { - fn sol_get_rent_sysvar(&self, pointer: *mut u8) -> u64 { - unsafe { - #[allow(clippy::cast_ptr_alignment)] - let rent = pointer.cast::(); - *rent = self.rent; - } - - 0 - } - - fn sol_log(&self, message: &str) { - info!("{}", message); - } - - fn sol_log_data(&self, fields: &[&[u8]]) { - let mut messages: Vec = Vec::new(); - - for f in fields { - if let Ok(str) = String::from_utf8(f.to_vec()) { - messages.push(str); - } else { - messages.push(hex::encode(f)); - } - } - - info!("Program Data: {}", messages.join(" ")); - } -} diff --git a/evm_loader/lib/src/tracing/mod.rs b/evm_loader/lib/src/tracing/mod.rs new file mode 100644 index 000000000..e510a84a8 --- /dev/null +++ b/evm_loader/lib/src/tracing/mod.rs @@ -0,0 +1,92 @@ +use std::collections::HashMap; + +use ethnum::U256; +use serde_json::Value; +use web3::types::{Bytes, H256}; + +use evm_loader::types::Address; + +pub mod tracers; + +/// See +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockOverrides { + pub number: Option, + #[allow(unused)] + pub difficulty: Option, // NOT SUPPORTED by Neon EVM + pub time: Option, + #[allow(unused)] + pub gas_limit: Option, // NOT SUPPORTED BY Neon EVM + #[allow(unused)] + pub coinbase: Option
, // NOT SUPPORTED BY Neon EVM + #[allow(unused)] + pub random: Option, // NOT SUPPORTED BY Neon EVM + #[allow(unused)] + pub base_fee: Option, // NOT SUPPORTED BY Neon EVM +} + +/// See +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct AccountOverride { + pub nonce: Option, + pub code: Option, + pub balance: Option, + pub state: Option>, + pub state_diff: Option>, +} + +impl AccountOverride { + #[must_use] + pub fn storage(&self, index: U256) -> Option<[u8; 32]> { + match (&self.state, &self.state_diff) { + (None, None) => None, + (Some(_), Some(_)) => { + panic!("Account has both `state` and `stateDiff` overrides") + } + (Some(state), None) => { + return state + .get(&H256::from(index.to_be_bytes())) + .map(|value| value.to_fixed_bytes()) + } + (None, Some(state_diff)) => state_diff + .get(&H256::from(index.to_be_bytes())) + .map(|v| v.to_fixed_bytes()), + } + } +} + +/// See +pub type AccountOverrides = HashMap; + +/// See +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(clippy::module_name_repetitions, clippy::struct_excessive_bools)] +pub struct TraceConfig { + #[serde(default)] + pub enable_memory: bool, + #[serde(default)] + pub disable_storage: bool, + #[serde(default)] + pub disable_stack: bool, + #[serde(default)] + pub enable_return_data: bool, + #[serde(default)] + pub limit: usize, + pub tracer: Option, + pub timeout: Option, + pub tracer_config: Option, +} + +/// See +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(clippy::module_name_repetitions)] +pub struct TraceCallConfig { + #[serde(flatten)] + pub trace_config: TraceConfig, + pub block_overrides: Option, + pub state_overrides: Option, +} diff --git a/evm_loader/lib/src/tracing/tracers/call_tracer.rs b/evm_loader/lib/src/tracing/tracers/call_tracer.rs new file mode 100644 index 000000000..eac19027b --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/call_tracer.rs @@ -0,0 +1,309 @@ +use crate::tracing::tracers::state_diff::to_web3_u256; +use crate::tracing::tracers::Tracer; +use crate::tracing::TraceConfig; +use crate::types::TxParams; +use async_trait::async_trait; +use evm_loader::error::{format_revert_error, format_revert_panic}; +use evm_loader::evm::database::Database; +use evm_loader::evm::opcode_table::Opcode; +use evm_loader::evm::tracing::{Event, EventListener}; +use evm_loader::evm::{opcode_table, Context, ExitStatus}; +use evm_loader::types::Address; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use web3::types::{Bytes, H256, U256}; + +pub struct CallTracer { + config: CallTracerConfig, + call_stack: Vec, + depth: usize, +} + +impl CallTracer { + pub fn new(trace_config: TraceConfig, tx: &TxParams) -> Self { + Self { + config: trace_config.into(), + call_stack: vec![CallFrame { + gas: tx.gas_limit.map(to_web3_u256).unwrap_or_default(), + gas_used: tx.actual_gas_used.map(to_web3_u256).unwrap_or_default(), + ..CallFrame::default() + }], + depth: 0, + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CallTracerConfig { + #[serde(default)] + pub only_top_call: bool, + // If true, call tracer won't collect any subcalls + #[serde(default)] + pub with_log: bool, // If true, call tracer will collect event logs +} + +impl From for CallTracerConfig { + fn from(trace_config: TraceConfig) -> Self { + let tracer_call_config = trace_config + .tracer_config + .expect("tracer_config should not be None for \"callTracer\""); + serde_json::from_value(tracer_call_config) + .expect("tracer_config should be CallTracerConfig") + } +} + +#[derive(Serialize)] +pub struct CallLog { + address: Address, + topics: Vec, + data: Bytes, + // Position of the log relative to subcalls within the same trace + // See https://github.com/ethereum/go-ethereum/pull/28389 for details + position: U256, +} + +#[derive(Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CallFrame { + from: Address, + gas: U256, + gas_used: U256, + #[serde(skip_serializing_if = "Option::is_none")] + to: Option
, + input: Bytes, + #[serde(skip_serializing_if = "is_empty")] + output: Bytes, + #[serde(skip_serializing_if = "String::is_empty")] + error: String, + #[serde(skip_serializing_if = "String::is_empty")] + revert_reason: String, + #[serde(skip_serializing_if = "Vec::is_empty")] + calls: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + logs: Vec, + // Placed at end on purpose. The RLP will be decoded to 0 instead of + // nil if there are non-empty elements after in the struct. + #[serde(skip_serializing_if = "Option::is_none")] + value: Option, + #[serde(rename = "type")] + type_string: Opcode, +} + +impl CallFrame { + fn process_output(&mut self, status: ExitStatus) { + if status.is_succeed().unwrap_or_default() { + self.output = status.into_result().unwrap_or_default().into(); + return; + } + + if let ExitStatus::Revert(_) = status { + self.error = "execution reverted".to_string(); + } + + if self.type_string == opcode_table::CREATE || self.type_string == opcode_table::CREATE2 { + self.to = None; + } + + self.output = status.into_result().unwrap_or_default().into(); + self.revert_reason = format_revert_message(&self.output.0); + } + + // clear_failed_logs clears the logs of a callframe and all its children + // in case of execution failure. + fn clear_failed_logs(&mut self, parent_failed: bool) { + let failed = !self.error.is_empty() || parent_failed; + if failed { + self.logs.clear(); + } + for call in &mut self.calls { + call.clear_failed_logs(failed); + } + } +} + +fn format_revert_message(msg: &[u8]) -> String { + if let Some(reason) = format_revert_error(msg) { + return reason.to_string(); + } + + if let Some(reason) = format_revert_panic(msg) { + return get_panic_message(reason); + } + + String::new() +} + +fn get_panic_message(reason: ethnum::U256) -> String { + let reason = reason.as_u64(); + PANIC_REASONS + .get(&reason) + .map(|s| (*s).to_string()) + .unwrap_or(format!("unknown panic code: {reason:#x}")) +} + +lazy_static! { + // panic_reasons map is for readable panic codes + // see this linkage for the details + // https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require + // the reason string list is copied from ethers.js + // https://github.com/ethers-io/ethers.js/blob/fa3a883ff7c88611ce766f58bdd4b8ac90814470/src.ts/abi/interface.ts#L207-L218 + static ref PANIC_REASONS: HashMap = HashMap::from([ + (0x00, "generic panic"), + (0x01, "assert(false)"), + (0x11, "arithmetic underflow or overflow"), + (0x12, "division or modulo by zero"), + (0x21, "enum overflow"), + (0x22, "invalid encoded storage byte array accessed"), + (0x31, "out-of-bounds array access; popping on an empty array"), + (0x32, "out-of-bounds access of an array or bytesN"), + (0x41, "out of memory"), + (0x51, "uninitialized function"), + ]); +} + +fn is_empty(bytes: &Bytes) -> bool { + bytes.0.is_empty() +} + +#[async_trait(?Send)] +impl EventListener for CallTracer { + async fn event( + &mut self, + _executor_state: &impl Database, + event: Event, + ) -> evm_loader::error::Result<()> { + match event { + Event::BeginVM { + context, + opcode, + input, + .. + } => { + self.depth += 1; + self.handle_begin_vm(context, opcode, input); + } + Event::EndVM { status, .. } => { + self.handle_end_vm(status); + self.depth -= 1; + } + Event::BeginStep { + context, + opcode, + stack, + memory, + .. + } => { + // Only logs need to be captured via opcode processing + if !self.config.with_log { + return Ok(()); + } + + // Avoid processing nested calls when only caring about top call + if self.config.only_top_call && self.depth > 1 { + return Ok(()); + } + + match opcode { + opcode_table::LOG0 + | opcode_table::LOG1 + | opcode_table::LOG2 + | opcode_table::LOG3 + | opcode_table::LOG4 => { + let size = (opcode.0 - opcode_table::LOG0.0) as usize; + + let m_start = U256::from(stack[stack.len() - 1]).as_usize(); + let m_size = U256::from(stack[stack.len() - 2]).as_usize(); + + let mut topics = Vec::with_capacity(size); + + for i in 0..size { + topics.push(H256::from(stack[stack.len() - 2 - (i + 1)])); + } + + let call_log = CallLog { + address: context.contract, + topics, + data: memory[m_start..m_start + m_size].to_vec().into(), + position: self.call_stack.last().unwrap().calls.len().into(), + }; + + self.call_stack.last_mut().unwrap().logs.push(call_log); + } + _ => {} + } + } + } + + Ok(()) + } +} + +impl CallTracer { + fn handle_begin_vm(&mut self, context: Context, opcode: Opcode, input: Vec) { + if self.depth == 1 { + let call_frame = &mut self.call_stack[0]; + call_frame.from = context.caller; + call_frame.to = context.code_address.or(Some(context.contract)); + call_frame.input = input.into(); + call_frame.value = Some(to_web3_u256(context.value)); + call_frame.type_string = opcode; + return; + } + + if self.config.only_top_call { + return; + } + + self.call_stack.push(CallFrame { + from: context.caller, + to: context.code_address.or(Some(context.contract)), + input: input.into(), + value: Some(to_web3_u256(context.value)), + type_string: opcode, + ..CallFrame::default() + }); + } + + fn handle_end_vm(&mut self, status: ExitStatus) { + if self.depth == 1 { + self.call_stack[0].process_output(status); + if self.config.with_log { + self.call_stack[0].clear_failed_logs(false); + } + return; + } + + if self.config.only_top_call { + return; + } + + if self.call_stack.len() <= 1 { + return; + } + + let mut call_frame = self.call_stack.pop().unwrap(); + + call_frame.process_output(status); + + self.call_stack.last_mut().unwrap().calls.push(call_frame); + } +} + +impl Tracer for CallTracer { + fn into_traces(mut self, emulator_gas_used: u64) -> Value { + assert!( + self.call_stack.len() == 1, + "incorrect number of top-level calls" + ); + + let call_frame = &mut self.call_stack[0]; + if call_frame.gas_used.is_zero() { + call_frame.gas_used = U256::from(emulator_gas_used); + } + + serde_json::to_value(call_frame).expect("serialization should not fail") + } +} diff --git a/evm_loader/lib/src/tracing/tracers/mod.rs b/evm_loader/lib/src/tracing/tracers/mod.rs new file mode 100644 index 000000000..3c2996334 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/mod.rs @@ -0,0 +1,76 @@ +use crate::tracing::tracers::call_tracer::CallTracer; +use crate::tracing::tracers::openeth::tracer::OpenEthereumTracer; +use crate::tracing::tracers::prestate_tracer::tracer::PrestateTracer; +use crate::tracing::tracers::struct_logger::StructLogger; +use crate::tracing::TraceConfig; +use crate::types::TxParams; +use async_trait::async_trait; +use enum_dispatch::enum_dispatch; +use evm_loader::evm::database::Database; +use evm_loader::evm::tracing::Event; +use evm_loader::evm::tracing::EventListener; +use serde_json::Value; + +pub mod call_tracer; +pub mod openeth; +pub mod prestate_tracer; +pub mod state_diff; +pub mod struct_logger; + +#[enum_dispatch(Tracer)] +pub enum TracerTypeEnum { + StructLogger(StructLogger), + OpenEthereumTracer(OpenEthereumTracer), + PrestateTracer(PrestateTracer), + CallTracer(CallTracer), +} + +// cannot use enum_dispatch because of trait and enum in different crates +#[async_trait(?Send)] +impl EventListener for TracerTypeEnum { + async fn event( + &mut self, + executor_state: &impl Database, + event: Event, + ) -> evm_loader::error::Result<()> { + match self { + Self::StructLogger(tracer) => tracer.event(executor_state, event).await, + Self::OpenEthereumTracer(tracer) => tracer.event(executor_state, event).await, + Self::PrestateTracer(tracer) => tracer.event(executor_state, event).await, + Self::CallTracer(tracer) => tracer.event(executor_state, event).await, + } + } +} + +#[enum_dispatch] +pub trait Tracer: EventListener { + fn into_traces(self, emulator_gas_used: u64) -> Value; +} + +pub fn new_tracer( + tx: &TxParams, + trace_config: TraceConfig, +) -> evm_loader::error::Result { + match trace_config.tracer.as_deref() { + None | Some("") => Ok(TracerTypeEnum::StructLogger(StructLogger::new( + trace_config, + tx, + ))), + Some("openethereum") => Ok(TracerTypeEnum::OpenEthereumTracer(OpenEthereumTracer::new( + trace_config, + tx, + ))), + Some("prestateTracer") => Ok(TracerTypeEnum::PrestateTracer(PrestateTracer::new( + trace_config, + tx, + ))), + Some("callTracer") => Ok(TracerTypeEnum::CallTracer(CallTracer::new( + trace_config, + tx, + ))), + _ => Err(evm_loader::error::Error::Custom(format!( + "Unsupported tracer: {:?}", + trace_config.tracer + ))), + } +} diff --git a/evm_loader/lib/src/tracing/tracers/openeth/mod.rs b/evm_loader/lib/src/tracing/tracers/openeth/mod.rs new file mode 100644 index 000000000..99c667778 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/openeth/mod.rs @@ -0,0 +1,3 @@ +pub mod state_diff; +pub mod tracer; +pub mod types; diff --git a/evm_loader/lib/src/tracing/tracers/openeth/state_diff.rs b/evm_loader/lib/src/tracing/tracers/openeth/state_diff.rs new file mode 100644 index 000000000..8fb4611d1 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/openeth/state_diff.rs @@ -0,0 +1,73 @@ +use crate::tracing::tracers::state_diff::StateMap; +use std::collections::BTreeMap; +use web3::types::{AccountDiff, ChangedType, Diff, StateDiff, H160, H256}; + +#[must_use] +pub fn into_state_diff(state_map: StateMap) -> StateDiff { + let mut state_diff = BTreeMap::new(); + + for (address, states) in state_map { + let pre_account = states.pre; + let post_account = states.post; + + if pre_account.is_empty() { + state_diff.insert( + H160::from(address.as_bytes()), + AccountDiff { + balance: build_diff(None, Some(post_account.balance)), + nonce: build_diff(None, Some(post_account.nonce.into())), + code: build_diff(None, Some(post_account.code.clone())), + storage: storage_diff(&pre_account.storage, &post_account.storage), + }, + ); + } else { + state_diff.insert( + H160::from(address.as_bytes()), + AccountDiff { + balance: build_diff(Some(pre_account.balance), Some(post_account.balance)), + nonce: build_diff( + Some(pre_account.nonce.into()), + Some(post_account.nonce.into()), + ), + code: build_diff( + Some(pre_account.code.clone()), + Some(post_account.code.clone()), + ), + storage: storage_diff(&pre_account.storage, &post_account.storage), + }, + ); + } + } + + StateDiff(state_diff) +} + +fn storage_diff( + account_initial_storage: &BTreeMap, + account_final_storage: &BTreeMap, +) -> BTreeMap> { + let mut storage_diff = BTreeMap::new(); + + for (key, initial_value) in account_initial_storage { + let final_value = account_final_storage.get(key).copied(); + + storage_diff.insert(*key, build_diff(Some(*initial_value), final_value)); + } + + storage_diff +} + +fn build_diff(from: Option, to: Option) -> Diff { + match (from, to) { + (None, Some(to)) => Diff::Born(to), + (None, None) => Diff::Same, + (Some(from), None) => Diff::Died(from), + (Some(from), Some(to)) => { + if from == to { + Diff::Same + } else { + Diff::Changed(ChangedType { from, to }) + } + } + } +} diff --git a/evm_loader/lib/src/tracing/tracers/openeth/tracer.rs b/evm_loader/lib/src/tracing/tracers/openeth/tracer.rs new file mode 100644 index 000000000..a60384192 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/openeth/tracer.rs @@ -0,0 +1,72 @@ +use async_trait::async_trait; + +use evm_loader::evm::database::Database; +use serde_json::Value; +use web3::types::Bytes; + +use evm_loader::evm::tracing::{Event, EventListener}; + +use crate::tracing::tracers::openeth::state_diff::into_state_diff; +use crate::tracing::tracers::openeth::types::{CallAnalytics, TraceResults}; +use crate::tracing::tracers::state_diff::StateDiffTracer; +use crate::tracing::tracers::Tracer; +use crate::tracing::TraceConfig; +use crate::types::TxParams; + +pub struct OpenEthereumTracer { + output: Option, + call_analytics: CallAnalytics, + state_diff_tracer: StateDiffTracer, +} + +impl OpenEthereumTracer { + #[must_use] + pub fn new(trace_config: TraceConfig, tx: &TxParams) -> Self { + Self { + output: None, + call_analytics: trace_config.into(), + state_diff_tracer: StateDiffTracer::new(tx), + } + } +} + +impl From for CallAnalytics { + fn from(trace_config: TraceConfig) -> Self { + let trace_call_config = trace_config + .tracer_config + .expect("tracer_config should not be None for \"openethereum\" tracer"); + serde_json::from_value(trace_call_config).expect("tracer_config should be CallAnalytics") + } +} + +#[async_trait(?Send)] +impl EventListener for OpenEthereumTracer { + async fn event( + &mut self, + executor_state: &impl Database, + event: Event, + ) -> evm_loader::error::Result<()> { + if let Event::EndVM { status, .. } = &event { + self.output = status.clone().into_result().map(Into::into); + } + self.state_diff_tracer.event(executor_state, event).await + } +} + +impl Tracer for OpenEthereumTracer { + fn into_traces(self, emulator_gas_used: u64) -> Value { + serde_json::to_value(TraceResults { + output: self.output.unwrap_or_default(), + trace: vec![], + vm_trace: None, + state_diff: if self.call_analytics.state_diffing { + Some(into_state_diff( + self.state_diff_tracer.into_state_map(emulator_gas_used), + )) + } else { + None + }, + }) + .expect("serialization should not fail") + } +} diff --git a/evm_loader/lib/src/tracing/tracers/openeth/types.rs b/evm_loader/lib/src/tracing/tracers/openeth/types.rs new file mode 100644 index 000000000..e2d438ac1 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/openeth/types.rs @@ -0,0 +1,406 @@ +/// Types copied from +use std::fmt; + +use serde::ser::SerializeStruct; +use serde::{Deserialize, Serialize, Serializer}; +use web3::types::{Bytes, StateDiff, H160, U256}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +/// A diff of some chunk of memory. +pub struct TraceResults { + /// The output of the call/create + pub output: Bytes, + /// The transaction trace. + pub state_diff: Option, + /// The transaction trace. + pub trace: Vec, + /// The transaction trace. + pub vm_trace: Option, +} + +/// Trace +#[derive(Debug, Clone)] +pub struct Trace { + /// Trace address + trace_address: Vec, + /// Subtraces + subtraces: usize, + /// Action + action: Action, + /// Result + result: Res, +} + +impl Serialize for Trace { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut struc = serializer.serialize_struct("Trace", 4)?; + match self.action { + Action::Call(ref call) => { + struc.serialize_field("type", "call")?; + struc.serialize_field("action", call)?; + } + Action::Create(ref create) => { + struc.serialize_field("type", "create")?; + struc.serialize_field("action", create)?; + } + Action::Suicide(ref suicide) => { + struc.serialize_field("type", "suicide")?; + struc.serialize_field("action", suicide)?; + } + Action::Reward(ref reward) => { + struc.serialize_field("type", "reward")?; + struc.serialize_field("action", reward)?; + } + } + + match self.result { + Res::Call(ref call) => struc.serialize_field("result", call)?, + Res::Create(ref create) => struc.serialize_field("result", create)?, + Res::FailedCall(ref error) | Res::FailedCreate(ref error) => { + struc.serialize_field("error", &error.to_string())?; + } + Res::None => struc.serialize_field("result", &None as &Option)?, + } + + struc.serialize_field("traceAddress", &self.trace_address)?; + struc.serialize_field("subtraces", &self.subtraces)?; + + struc.end() + } +} + +/// Action +#[derive(Debug, Clone)] +pub enum Action { + /// Call + Call(Call), + /// Create + Create(Create), + /// Suicide + Suicide(Suicide), + /// Reward + Reward(Reward), +} + +/// Call response +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Call { + /// Sender + from: H160, + /// Recipient + to: H160, + /// Transfered Value + value: U256, + /// Gas + gas: U256, + /// Input data + input: Bytes, + /// The type of the call. + call_type: CallType, +} + +/// Call type. +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum CallType { + /// None + None, + /// Call + Call, + /// Call code + CallCode, + /// Delegate call + DelegateCall, + /// Static call + StaticCall, +} + +/// Create response +#[derive(Debug, Clone, Serialize)] +pub struct Create { + /// Sender + from: H160, + /// Value + value: U256, + /// Gas + gas: U256, + /// Initialization code + init: Bytes, +} + +/// Suicide +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Suicide { + /// Address. + pub address: H160, + /// Refund address. + pub refund_address: H160, + /// Balance. + pub balance: U256, +} + +/// Reward action +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Reward { + /// Author's address. + pub author: H160, + /// Reward amount. + pub value: U256, + /// Reward type. + pub reward_type: RewardType, +} + +/// Reward type. +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum RewardType { + /// Block + Block, + /// Uncle + Uncle, + /// EmptyStep (AuthorityRound) + EmptyStep, + /// External (attributed as part of an external protocol) + External, +} + +#[derive(Debug, Clone, Serialize)] +/// A record of a full VM trace for a CALL/CREATE. +pub struct VMTrace { + /// The code to be executed. + pub code: Bytes, + /// The operations executed. + pub ops: Vec, +} + +#[derive(Debug, Clone, Serialize)] +/// A record of the execution of a single VM operation. +pub struct VMOperation { + /// The program counter. + pub pc: usize, + /// The gas cost for this instruction. + pub cost: u64, + /// Information concerning the execution of the operation. + pub ex: Option, + /// Subordinate trace of the CALL/CREATE if applicable. + #[serde(bound = "VMTrace: Serialize")] + pub sub: Option, +} + +#[derive(Debug, Clone, Serialize)] +/// A record of an executed VM operation. +pub struct VMExecutedOperation { + /// The total gas used. + pub used: u64, + /// The stack item placed, if any. + pub push: Vec, + /// If altered, the memory delta. + pub mem: Option, + /// The altered storage value, if any. + pub store: Option, +} + +#[derive(Debug, Clone, Serialize)] +/// A diff of some chunk of memory. +pub struct MemoryDiff { + /// Offset into memory the change begins. + pub off: usize, + /// The changed data. + pub data: Bytes, +} + +#[derive(Debug, Clone, Serialize)] +/// A diff of some storage value. +pub struct StorageDiff { + /// Which key in storage is changed. + pub key: U256, + /// What the value has been changed to. + pub val: U256, +} + +#[derive(Debug, Clone)] +pub enum Res { + /// Call + Call(CallResult), + /// Create + Create(CreateResult), + /// Call failure + FailedCall(TraceError), + /// Creation failure + FailedCreate(TraceError), + /// None + None, +} + +/// Call Result +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CallResult { + /// Gas used + gas_used: U256, + /// Output bytes + output: Bytes, +} + +/// Create Result +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateResult { + /// Gas used + gas_used: U256, + /// Code + code: Bytes, + /// Assigned address + address: H160, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TraceError { + /// `OutOfGas` is returned when transaction execution runs out of gas. + OutOfGas, + /// `BadJumpDestination` is returned when execution tried to move + /// to position that wasn't marked with JUMPDEST instruction + BadJumpDestination, + /// `BadInstructions` is returned when given instruction is not supported + BadInstruction, + /// `StackUnderflow` when there is not enough stack elements to execute instruction + StackUnderflow, + /// When execution would exceed defined Stack Limit + OutOfStack, + /// When there is not enough subroutine stack elements to return from + SubStackUnderflow, + /// When execution would exceed defined subroutine Stack Limit + OutOfSubStack, + /// When the code walks into a subroutine, that is not allowed + InvalidSubEntry, + /// When builtin contract failed on input data + BuiltIn, + /// Returned on evm internal error. Should never be ignored during development. + /// Likely to cause consensus issues. + Internal, + /// When execution tries to modify the state in static context + MutableCallInStaticContext, + /// When invalid code was attempted to deploy + InvalidCode, + /// Wasm error + Wasm, + /// Contract tried to access past the return data buffer. + OutOfBounds, + /// Execution has been reverted with REVERT instruction. + Reverted, +} + +impl fmt::Display for TraceError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::TraceError::{ + BadInstruction, BadJumpDestination, BuiltIn, Internal, InvalidCode, InvalidSubEntry, + MutableCallInStaticContext, OutOfBounds, OutOfGas, OutOfStack, OutOfSubStack, Reverted, + StackUnderflow, SubStackUnderflow, Wasm, + }; + let message = match *self { + OutOfGas => "Out of gas", + BadJumpDestination => "Bad jump destination", + BadInstruction => "Bad instruction", + StackUnderflow => "Stack underflow", + OutOfStack => "Out of stack", + SubStackUnderflow => "Subroutine stack underflow", + OutOfSubStack => "Subroutine stack overflow", + BuiltIn => "Built-in failed", + InvalidSubEntry => "Invalid subroutine entry", + InvalidCode => "Invalid code", + Wasm => "Wasm runtime error", + Internal => "Internal error", + MutableCallInStaticContext => "Mutable Call In Static Context", + OutOfBounds => "Out of bounds", + Reverted => "Reverted", + }; + message.fmt(f) + } +} + +pub type TraceOptions = Vec; + +#[must_use] +pub fn to_call_analytics(flags: &TraceOptions) -> CallAnalytics { + CallAnalytics { + transaction_tracing: flags.contains(&("trace".to_owned())), + vm_tracing: flags.contains(&("vmTrace".to_owned())), + state_diffing: flags.contains(&("stateDiff".to_owned())), + } +} + +/// Options concerning what analytics we run on the call. +#[derive(Eq, PartialEq, Default, Clone, Copy, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CallAnalytics { + /// Make a transaction trace. + pub transaction_tracing: bool, + /// Make a VM trace. + pub vm_tracing: bool, + /// Make a diff. + pub state_diffing: bool, +} + +#[cfg(test)] +mod tests { + use serde_json; + + use super::*; + + #[test] + fn test_vmtrace_serialize() { + let t = VMTrace { + code: vec![0, 1, 2, 3].into(), + ops: vec![ + VMOperation { + pc: 0, + cost: 10, + ex: None, + sub: None, + }, + VMOperation { + pc: 1, + cost: 11, + ex: Some(VMExecutedOperation { + used: 10, + push: vec![69.into()], + mem: None, + store: None, + }), + sub: Some(VMTrace { + code: vec![0].into(), + ops: vec![VMOperation { + pc: 0, + cost: 0, + ex: Some(VMExecutedOperation { + used: 10, + push: vec![42.into()], + mem: Some(MemoryDiff { + off: 42, + data: vec![1, 2, 3].into(), + }), + store: Some(StorageDiff { + key: 69.into(), + val: 42.into(), + }), + }), + sub: None, + }], + }), + }, + ], + }; + let serialized = serde_json::to_string(&t).unwrap(); + assert_eq!( + serialized, + r#"{"code":"0x00010203","ops":[{"pc":0,"cost":10,"ex":null,"sub":null},{"pc":1,"cost":11,"ex":{"used":10,"push":["0x45"],"mem":null,"store":null},"sub":{"code":"0x00","ops":[{"pc":0,"cost":0,"ex":{"used":10,"push":["0x2a"],"mem":{"off":42,"data":"0x010203"},"store":{"key":"0x45","val":"0x2a"}},"sub":null}]}}]}"# + ); + } +} diff --git a/evm_loader/lib/src/tracing/tracers/prestate_tracer/mod.rs b/evm_loader/lib/src/tracing/tracers/prestate_tracer/mod.rs new file mode 100644 index 000000000..bca8669c3 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/prestate_tracer/mod.rs @@ -0,0 +1,4 @@ +mod state_diff; +pub mod tracer; + +pub use state_diff::{PrestateTracerAccount, PrestateTracerDiffModeResult, PrestateTracerState}; diff --git a/evm_loader/lib/src/tracing/tracers/prestate_tracer/state_diff.rs b/evm_loader/lib/src/tracing/tracers/prestate_tracer/state_diff.rs new file mode 100644 index 000000000..5245bb758 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/prestate_tracer/state_diff.rs @@ -0,0 +1,136 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; +use web3::types::{Bytes, H256, U256}; + +use crate::tracing::tracers::state_diff::StateMap; +use evm_loader::types::Address; + +/// See +pub type PrestateTracerState = BTreeMap; + +/// See +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct PrestateTracerAccount { + #[serde(skip_serializing_if = "Option::is_none")] + pub balance: Option, + #[serde(skip_serializing_if = "is_empty")] + pub code: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, + #[serde(default)] + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + pub storage: BTreeMap, +} + +fn is_empty(bytes: &Option) -> bool { + bytes.as_ref().map_or(true, |bytes| bytes.0.is_empty()) +} + +/// See +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct PrestateTracerDiffModeResult { + pub post: PrestateTracerState, + pub pre: PrestateTracerState, +} + +pub fn build_prestate_tracer_pre_state(state_map: StateMap) -> PrestateTracerState { + let mut result = BTreeMap::new(); + + for (address, states) in state_map { + let pre_account = states.pre; + + if pre_account.is_empty() { + continue; + } + + result.insert( + address, + PrestateTracerAccount { + balance: Some(pre_account.balance), + code: Some(pre_account.code), + nonce: Some(pre_account.nonce), + storage: pre_account.storage, + }, + ); + } + + result +} + +/// See +pub fn build_prestate_tracer_diff_mode_result(state_map: StateMap) -> PrestateTracerDiffModeResult { + let mut pre = build_prestate_tracer_pre_state(state_map.clone()); + + let mut post = BTreeMap::new(); + + for (address, states) in state_map { + let pre_account = states.pre; + let post_account = states.post; + + let mut modified = false; + + let balance = if post_account.balance == pre_account.balance { + None + } else { + modified = true; + Some(post_account.balance) + }; + + let code = if post_account.code == pre_account.code { + None + } else { + modified = true; + Some(post_account.code.clone()) + }; + + let nonce = if post_account.nonce == pre_account.nonce { + None + } else { + modified = true; + Some(post_account.nonce) + }; + + let mut storage = BTreeMap::new(); + + for (key, initial_value) in pre_account.storage { + // don't include the empty slot + if initial_value == H256::zero() { + pre.entry(address).and_modify(|account| { + account.storage.remove(&key); + }); + } + + let final_value = post_account.storage.get(&key).copied().unwrap_or_default(); + + // Omit unchanged slots + if initial_value == final_value { + pre.entry(address).and_modify(|account| { + account.storage.remove(&key); + }); + } else { + modified = true; + if final_value != H256::zero() { + storage.insert(key, final_value); + } + } + } + + if modified { + post.insert( + address, + PrestateTracerAccount { + balance, + code, + nonce, + storage, + }, + ); + } else { + // if state is not modified, then no need to include into the pre state + pre.remove(&address); + } + } + + PrestateTracerDiffModeResult { post, pre } +} diff --git a/evm_loader/lib/src/tracing/tracers/prestate_tracer/tracer.rs b/evm_loader/lib/src/tracing/tracers/prestate_tracer/tracer.rs new file mode 100644 index 000000000..1c0916013 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/prestate_tracer/tracer.rs @@ -0,0 +1,72 @@ +use crate::tracing::tracers::prestate_tracer::state_diff::{ + build_prestate_tracer_diff_mode_result, build_prestate_tracer_pre_state, +}; +use crate::tracing::tracers::state_diff::StateDiffTracer; +use crate::tracing::tracers::Tracer; +use crate::tracing::TraceConfig; +use crate::types::TxParams; +use async_trait::async_trait; +use evm_loader::evm::database::Database; +use evm_loader::evm::tracing::{Event, EventListener}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +/// See +pub struct PrestateTracer { + config: PrestateTracerConfig, + state_diff_tracer: StateDiffTracer, +} + +impl PrestateTracer { + #[must_use] + pub fn new(trace_config: TraceConfig, tx: &TxParams) -> Self { + Self { + config: trace_config.into(), + state_diff_tracer: StateDiffTracer::new(tx), + } + } +} + +impl From for PrestateTracerConfig { + fn from(trace_config: TraceConfig) -> Self { + trace_config + .tracer_config + .map(|tracer_config| { + serde_json::from_value(tracer_config) + .expect("tracer_config should be PrestateTracerConfig") + }) + .unwrap_or_default() + } +} + +/// See +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct PrestateTracerConfig { + #[serde(default)] + pub diff_mode: bool, +} + +#[async_trait(?Send)] +impl EventListener for PrestateTracer { + async fn event( + &mut self, + executor_state: &impl Database, + event: Event, + ) -> evm_loader::error::Result<()> { + self.state_diff_tracer.event(executor_state, event).await + } +} + +impl Tracer for PrestateTracer { + fn into_traces(self, emulator_gas_used: u64) -> Value { + let state_map = self.state_diff_tracer.into_state_map(emulator_gas_used); + + if self.config.diff_mode { + serde_json::to_value(build_prestate_tracer_diff_mode_result(state_map)) + } else { + serde_json::to_value(build_prestate_tracer_pre_state(state_map)) + } + .expect("serialization should not fail") + } +} diff --git a/evm_loader/lib/src/tracing/tracers/state_diff.rs b/evm_loader/lib/src/tracing/tracers/state_diff.rs new file mode 100644 index 000000000..f01b6b328 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/state_diff.rs @@ -0,0 +1,285 @@ +use arrayref::array_ref; +use async_trait::async_trait; +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; + +use ethnum::U256; +use web3::types::{Bytes, H256}; + +use crate::types::TxParams; +use evm_loader::evm::database::Database; +use evm_loader::evm::tracing::{Event, EventListener}; +use evm_loader::evm::{opcode_table, Buffer}; +use evm_loader::types::Address; +use serde::{Deserialize, Serialize}; + +pub type StateMap = BTreeMap; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct Account { + pub balance: web3::types::U256, + pub code: Bytes, + pub nonce: u64, + pub storage: BTreeMap, +} + +impl Account { + #[must_use] + pub fn is_empty(&self) -> bool { + self.balance.is_zero() && self.nonce == 0 && self.code.0.is_empty() + } +} + +// TODO NDEV-2451 - Add operator balance diff to pre and post state +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct States { + pub post: Account, + pub pre: Account, +} + +fn map_code(buffer: &Buffer) -> Bytes { + buffer.to_vec().into() +} + +pub(crate) fn to_web3_u256(v: U256) -> web3::types::U256 { + web3::types::U256::from(v.to_be_bytes()) +} + +#[derive(Default, Debug)] +pub struct StateDiffTracer { + from: Address, + gas_price: web3::types::U256, + tx_fee: web3::types::U256, + depth: usize, + state_map: StateMap, +} + +#[async_trait(?Send)] +impl EventListener for StateDiffTracer { + /// See + async fn event( + &mut self, + executor_state: &impl Database, + event: Event, + ) -> evm_loader::error::Result<()> { + match event { + Event::BeginVM { + context, + chain_id, + opcode, + .. + } => { + self.depth += 1; + + if self.depth == 1 { + self.lookup_account(executor_state, chain_id, context.caller) + .await?; + self.lookup_account(executor_state, chain_id, context.contract) + .await?; + + let value = to_web3_u256(context.value); + + self.state_map + .entry(context.caller) + .or_default() + .pre + .balance += value; + + self.state_map + .entry(context.contract) + .or_default() + .pre + .balance -= value; + + self.state_map.entry(context.caller).or_default().pre.nonce -= 1; + + if opcode == opcode_table::CREATE { + self.state_map + .entry(context.contract) + .or_default() + .pre + .nonce -= 1; + } + } + } + Event::EndVM { + context, chain_id, .. + } => { + if self.depth == 1 { + for (address, states) in &mut self.state_map { + states.post = Account { + balance: to_web3_u256( + executor_state.balance(*address, chain_id).await?, + ), + code: map_code(&executor_state.code(*address).await?), + nonce: executor_state.nonce(*address, chain_id).await?, + storage: { + let mut new_storage = BTreeMap::new(); + + for key in states.pre.storage.keys() { + new_storage.insert( + *key, + H256::from( + executor_state + .storage( + *address, + U256::from_be_bytes(key.to_fixed_bytes()), + ) + .await?, + ), + ); + } + + new_storage + }, + }; + } + + self.state_map + .entry(context.caller) + .or_default() + .post + .balance -= self.tx_fee; + } + + self.depth -= 1; + } + Event::BeginStep { + context, + chain_id, + opcode, + stack, + memory, + .. + } => { + let contract = context.contract; + match opcode { + opcode_table::SLOAD | opcode_table::SSTORE if !stack.is_empty() => { + let index = H256::from(&stack[stack.len() - 1]); + self.lookup_storage(executor_state, contract, index).await?; + } + opcode_table::EXTCODECOPY + | opcode_table::EXTCODEHASH + | opcode_table::EXTCODESIZE + | opcode_table::BALANCE + | opcode_table::SENDALL + if !stack.is_empty() => + { + let address = Address::from(*array_ref!(stack[stack.len() - 1], 12, 20)); + self.lookup_account(executor_state, chain_id, address) + .await?; + } + opcode_table::DELEGATECALL + | opcode_table::CALL + | opcode_table::STATICCALL + | opcode_table::CALLCODE + if stack.len() >= 5 => + { + let address = Address::from(*array_ref!(stack[stack.len() - 2], 12, 20)); + self.lookup_account(executor_state, chain_id, address) + .await?; + } + opcode_table::CREATE => { + let nonce = executor_state + .nonce(contract, context.contract_chain_id) + .await?; + + let created_address = Address::from_create(&contract, nonce); + self.lookup_account(executor_state, chain_id, created_address) + .await?; + } + opcode_table::CREATE2 if stack.len() >= 4 => { + let offset = U256::from_be_bytes(stack[stack.len() - 2]).as_usize(); + let length = U256::from_be_bytes(stack[stack.len() - 3]).as_usize(); + let salt = stack[stack.len() - 4]; + + let initialization_code = &memory[offset..offset + length]; + let created_address = + Address::from_create2(&contract, &salt, initialization_code); + self.lookup_account(executor_state, chain_id, created_address) + .await?; + } + _ => {} + } + } + } + Ok(()) + } +} + +impl StateDiffTracer { + pub fn new(tx: &TxParams) -> Self { + Self { + from: tx.from, + gas_price: tx.gas_price.map(to_web3_u256).unwrap_or_default(), + tx_fee: to_web3_u256( + tx.actual_gas_used + .unwrap_or_default() + .saturating_mul(tx.gas_price.unwrap_or_default()), + ), + ..Self::default() + } + } + + /// See + + async fn lookup_account( + &mut self, + executor_state: &impl Database, + chain_id: u64, + address: Address, + ) -> evm_loader::error::Result<()> { + match self.state_map.entry(address) { + Entry::Vacant(entry) => { + entry.insert(States { + post: Account::default(), + pre: Account { + balance: to_web3_u256(executor_state.balance(address, chain_id).await?), + code: map_code(&executor_state.code(address).await?), + nonce: executor_state.nonce(address, chain_id).await?, + storage: BTreeMap::new(), + }, + }); + } + Entry::Occupied(_) => {} + }; + Ok(()) + } + + /// See + + async fn lookup_storage( + &mut self, + executor_state: &impl Database, + address: Address, + index: H256, + ) -> evm_loader::error::Result<()> { + match self + .state_map + .entry(address) + .or_default() + .pre + .storage + .entry(index) + { + Entry::Vacant(entry) => { + entry.insert(H256::from( + executor_state + .storage(address, U256::from_be_bytes(index.to_fixed_bytes())) + .await?, + )); + } + Entry::Occupied(_) => {} + }; + Ok(()) + } + #[must_use] + pub fn into_state_map(mut self, emulator_gas_used: u64) -> StateMap { + if self.tx_fee.is_zero() { + self.state_map.entry(self.from).or_default().post.balance -= + web3::types::U256::from(emulator_gas_used).saturating_mul(self.gas_price); + } + + self.state_map + } +} diff --git a/evm_loader/lib/src/tracing/tracers/struct_logger.rs b/evm_loader/lib/src/tracing/tracers/struct_logger.rs new file mode 100644 index 000000000..c512a67b7 --- /dev/null +++ b/evm_loader/lib/src/tracing/tracers/struct_logger.rs @@ -0,0 +1,291 @@ +use async_trait::async_trait; +use std::collections::BTreeMap; + +use ethnum::U256; +use evm_loader::evm::database::Database; +use serde::Serialize; +use serde_json::Value; +use web3::types::Bytes; + +use evm_loader::evm::opcode_table::Opcode; +use evm_loader::evm::tracing::{Event, EventListener}; +use evm_loader::evm::{opcode_table, ExitStatus}; +use evm_loader::types::Address; + +use crate::tracing::tracers::Tracer; +use crate::tracing::TraceConfig; +use crate::types::TxParams; + +/// `StructLoggerResult` groups all structured logs emitted by the EVM +/// while replaying a transaction in debug mode as well as transaction +/// execution status, the amount of gas used and the return value +/// see +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct StructLoggerResult { + /// Total used gas but include the refunded gas + gas: u64, + /// Is execution failed or not + failed: bool, + /// The data after execution or revert reason + return_value: String, + /// Logs emitted during execution + struct_logs: Vec, +} + +/// `StructLog` stores a structured log emitted by the EVM while replaying a +/// transaction in debug mode +/// see +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct StructLog { + /// Program counter. + pc: u64, + /// Operation name + op: Opcode, + /// Amount of used gas + gas: u64, + /// Gas cost for this instruction. + gas_cost: u64, + /// Current depth + depth: usize, + #[serde(skip_serializing_if = "Option::is_none")] + error: Option, + /// Snapshot of the current stack sate + #[serde(skip_serializing_if = "Option::is_none")] + stack: Option>, + #[serde(skip_serializing_if = "is_empty")] + return_data: Bytes, + /// Snapshot of the current memory sate + #[serde(skip_serializing_if = "Option::is_none")] + memory: Option>, // chunks of 32 bytes + /// Result of the step + /// Snapshot of the current storage + #[serde(skip_serializing_if = "Option::is_none")] + storage: Option>, + /// Refund counter + #[serde(skip_serializing_if = "is_zero")] + refund: u64, +} + +fn is_empty(bytes: &Bytes) -> bool { + bytes.0.is_empty() +} + +/// This is only used for serialize +#[allow(clippy::trivially_copy_pass_by_ref)] +const fn is_zero(num: &u64) -> bool { + *num == 0 +} + +pub struct StructLogger { + actual_gas_used: Option, + config: TraceConfig, + logs: Vec, + depth: usize, + storage: BTreeMap>, + exit_status: Option, +} + +impl StructLogger { + #[must_use] + pub fn new(config: TraceConfig, tx: &TxParams) -> Self { + Self { + actual_gas_used: tx.actual_gas_used, + config, + logs: vec![], + depth: 0, + storage: BTreeMap::new(), + exit_status: None, + } + } +} + +#[async_trait(?Send)] +impl EventListener for StructLogger { + /// See + async fn event( + &mut self, + executor_state: &impl Database, + event: Event, + ) -> evm_loader::error::Result<()> { + match event { + Event::BeginVM { .. } => { + self.depth += 1; + } + Event::EndVM { status, .. } => { + if self.depth == 1 { + self.exit_status = Some(status); + } + self.depth -= 1; + } + Event::BeginStep { + context, + opcode, + pc, + stack, + memory, + return_data, + .. + } => { + if self.config.limit > 0 && self.logs.len() >= self.config.limit { + return Ok(()); + } + + let storage = if self.config.disable_storage { + None + } else if opcode == opcode_table::SLOAD && !stack.is_empty() { + let index = U256::from_be_bytes(stack[stack.len() - 1]); + + self.storage.entry(context.contract).or_default().insert( + hex::encode(index.to_be_bytes()), + hex::encode(executor_state.storage(context.contract, index).await?), + ); + + Some( + self.storage + .get(&context.contract) + .cloned() + .unwrap_or_default(), + ) + } else if opcode == opcode_table::SSTORE && stack.len() >= 2 { + self.storage.entry(context.contract).or_default().insert( + hex::encode(stack[stack.len() - 1]), + hex::encode(stack[stack.len() - 2]), + ); + + Some( + self.storage + .get(&context.contract) + .cloned() + .unwrap_or_default(), + ) + } else { + None + }; + let stack = if self.config.disable_stack { + None + } else { + Some(stack.into_iter().map(U256::from_be_bytes).collect()) + }; + + let memory = if self.config.enable_memory { + Some(memory.chunks(32).map(hex::encode).collect()) + } else { + None + }; + + self.logs.push(StructLog { + pc: pc as u64, + op: opcode, + gas: 0, + gas_cost: 0, + depth: self.depth, + memory, + stack, + return_data: return_data.into(), + storage, + error: None, + refund: 0, + }); + } + }; + Ok(()) + } +} + +impl Tracer for StructLogger { + fn into_traces(self, emulator_gas_used: u64) -> Value { + let exit_status = self.exit_status.expect("Exit status should be set"); + let result = StructLoggerResult { + gas: self.actual_gas_used.map_or(emulator_gas_used, U256::as_u64), + failed: !exit_status + .is_succeed() + .expect("Emulation is not completed"), + return_value: hex::encode(exit_status.into_result().unwrap_or_default()), + struct_logs: self.logs, + }; + serde_json::to_value(result).expect("serialization should not fail") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serialize_struct_logger_result_all_fields() { + let struct_logger_result = StructLoggerResult { + gas: 20000, + failed: false, + return_value: "000000000000000000000000000000000000000000000000000000000000001b" + .to_string(), + struct_logs: vec![StructLog { + pc: 8, + op: opcode_table::PUSH2, + gas: 0, + gas_cost: 0, + depth: 1, + stack: Some(vec![U256::from(0u8), U256::from(1u8)]), + memory: Some(vec![ + "0000000000000000000000000000000000000000000000000000000000000000".to_string(), + "0000000000000000000000000000000000000000000000000000000000000000".to_string(), + "0000000000000000000000000000000000000000000000000000000000000080".to_string(), + ]), + return_data: vec![].into(), + storage: None, + refund: 0, + error: None, + }], + }; + assert_eq!(serde_json::to_string(&struct_logger_result).unwrap(), "{\"gas\":20000,\"failed\":false,\"returnValue\":\"000000000000000000000000000000000000000000000000000000000000001b\",\"structLogs\":[{\"pc\":8,\"op\":\"PUSH2\",\"gas\":0,\"gasCost\":0,\"depth\":1,\"stack\":[\"0x0\",\"0x1\"],\"memory\":[\"0000000000000000000000000000000000000000000000000000000000000000\",\"0000000000000000000000000000000000000000000000000000000000000000\",\"0000000000000000000000000000000000000000000000000000000000000080\"]}]}"); + } + + #[test] + fn test_serialize_struct_logger_result_no_optional_fields() { + let struct_logger_result = StructLoggerResult { + gas: 20000, + failed: false, + return_value: "000000000000000000000000000000000000000000000000000000000000001b" + .to_string(), + struct_logs: vec![StructLog { + pc: 0, + op: opcode_table::PUSH1, + gas: 0, + gas_cost: 0, + depth: 1, + stack: None, + memory: None, + return_data: vec![].into(), + storage: None, + refund: 0, + error: None, + }], + }; + assert_eq!(serde_json::to_string(&struct_logger_result).unwrap(), "{\"gas\":20000,\"failed\":false,\"returnValue\":\"000000000000000000000000000000000000000000000000000000000000001b\",\"structLogs\":[{\"pc\":0,\"op\":\"PUSH1\",\"gas\":0,\"gasCost\":0,\"depth\":1}]}"); + } + + #[test] + fn test_serialize_struct_logger_result_empty_stack_empty_memory() { + let struct_logger_result = StructLoggerResult { + gas: 20000, + failed: false, + return_value: "000000000000000000000000000000000000000000000000000000000000001b" + .to_string(), + struct_logs: vec![StructLog { + pc: 0, + op: opcode_table::PUSH1, + gas: 0, + gas_cost: 0, + depth: 1, + stack: Some(vec![]), + memory: Some(vec![]), + return_data: vec![].into(), + storage: None, + refund: 0, + error: None, + }], + }; + assert_eq!(serde_json::to_string(&struct_logger_result).unwrap(), "{\"gas\":20000,\"failed\":false,\"returnValue\":\"000000000000000000000000000000000000000000000000000000000000001b\",\"structLogs\":[{\"pc\":0,\"op\":\"PUSH1\",\"gas\":0,\"gasCost\":0,\"depth\":1,\"stack\":[],\"memory\":[]}]}"); + } +} diff --git a/evm_loader/lib/src/types/indexer_db.rs b/evm_loader/lib/src/types/indexer_db.rs deleted file mode 100644 index 3b2c84eab..000000000 --- a/evm_loader/lib/src/types/indexer_db.rs +++ /dev/null @@ -1,175 +0,0 @@ -use { - super::{do_connect, ChDbConfig, PgError, PgResult, TxParams}, - ethnum::U256, - evm_loader::types::Address, - solana_sdk::clock::Slot, - std::{ - convert::{TryFrom, TryInto}, - sync::Arc, - }, - tokio_postgres::Client, -}; - -#[derive(Debug, Clone)] -pub struct IndexerDb { - pub client: Arc, -} - -const TXPARAMS_FIELDS: &str = - "from_addr, COALESCE(to_addr, contract), calldata, value, gas_limit, nonce"; - -impl IndexerDb { - pub async fn new(config: &ChDbConfig) -> Self { - let client = do_connect( - &config.indexer_host, - &config.indexer_port, - &config.indexer_database, - &config.indexer_user, - &config.indexer_password, - ) - .await; - Self { - client: Arc::new(client), - } - } - - pub async fn get_sol_sig(&self, hash: &[u8; 32]) -> PgResult<[u8; 64]> { - let hex = format!("0x{}", hex::encode(hash)); - let row = self - .client - .query_one( - "SELECT S.sol_sig from solana_neon_transactions S, solana_blocks B \ - where S.block_slot = B.block_slot \ - and B.is_active = true \ - and S.neon_sig = $1", - &[&hex], - ) - .await?; - let sol_sig_b58: &str = row.try_get(0)?; - let sol_sig_b58 = sol_sig_b58.to_string(); - let sol_sig = bs58::decode(sol_sig_b58) - .into_vec() - .map_err(|e| PgError::Custom(format!("sol_sig_b58 cast error: {e}")))?; - sol_sig - .as_slice() - .try_into() - .map_err(|e| PgError::Custom(format!("sol_sig cast error: {e}"))) - } - - pub async fn get_slot(&self, hash: &[u8; 32]) -> PgResult { - let hex = format!("0x{}", hex::encode(hash)); - let row = self - .client - .query_one( - "SELECT min(S.block_slot) from solana_neon_transactions S, solana_blocks B \ - where S.block_slot = B.block_slot \ - and B.is_active = true \ - and S.neon_sig = $1", - &[&hex], - ) - .await?; - let slot: i64 = row.try_get(0)?; - u64::try_from(slot).map_err(|e| PgError::Custom(format!("slot cast error: {e}"))) - } - - #[allow(unused)] - pub async fn get_slot_by_block_hash(&self, block_hash: &[u8; 32]) -> PgResult { - let hex = format!("0x{}", hex::encode(block_hash)); - let row = self - .client - .query_one( - "SELECT block_slot FROM solana_blocks WHERE block_hash = $1 AND is_active = TRUE", - &[&hex], - ) - .await?; - - let slot: i64 = row - .try_get(0) - .map_err(std::convert::Into::::into)?; - - slot.try_into() - .map_err(|e| PgError::Custom(format!("slot cast error: {e}"))) - } - - pub async fn get_transaction_data(&self, hash: &[u8; 32]) -> PgResult { - let hex = format!("0x{}", hex::encode(hash)); - - let row = self - .client - .query_one( - &format!( - "select distinct {TXPARAMS_FIELDS} \ - from neon_transactions as t, solana_blocks as b \ - where t.block_slot = b.block_slot \ - and b.is_active = TRUE \ - and t.neon_sig = $1" - ), - &[&hex], - ) - .await?; - - Self::extract_transaction(&row) - } - - pub async fn get_block_transactions(&self, slot: u64) -> PgResult> { - let slot: i64 = slot - .try_into() - .map_err(|e| PgError::Custom(format!("slot cast error: {e}")))?; - - let rows = self - .client - .query( - &format!( - "\ - SELECT {TXPARAMS_FIELDS} \ - FROM neon_transactions t \ - INNER JOIN solana_blocks b ON t.block_slot = b.block_slot \ - WHERE b.is_active = TRUE AND t.block_slot = $1 \ - ORDER BY tx_idx\ - " - ), - &[&slot], - ) - .await?; - - let mut transactions = vec![]; - for row in rows { - transactions.push(Self::extract_transaction(&row)?); - } - - Ok(transactions) - } - - fn extract_transaction(row: &tokio_postgres::Row) -> PgResult { - let from: String = row.try_get(0)?; - let to: String = row.try_get(1)?; - let data: String = row.try_get(2)?; - let value: String = row.try_get(3)?; - let gas_limit: String = row.try_get(4)?; - let nonce: String = row.try_get(5)?; - - let from = Address::from_hex(&from.as_str()[2..]) - .map_err(|e| PgError::Custom(format!("from_address cast error: {e}")))?; - let to = Address::from_hex(&to.as_str()[2..]) - .map_err(|e| PgError::Custom(format!("to_address cast error: {e}")))?; - let data = hex::decode(&data.as_str()[2..]) - .map_err(|e| PgError::Custom(format!("data cast error: {e}")))?; - let value: U256 = U256::from_str_hex(&value) - .map_err(|e| PgError::Custom(format!("value cast error: {e}")))?; - let gas_limit: U256 = U256::from_str_hex(&gas_limit) - .map_err(|e| PgError::Custom(format!("gas_limit cast error: {e}")))?; - let nonce: u64 = U256::from_str_hex(&nonce) - .map_err(|e| PgError::Custom(format!("nonce cast error: {e}")))? - .try_into() - .map_err(|e| PgError::Custom(format!("nonce cast error: {e}")))?; - - Ok(TxParams { - nonce: Some(nonce), - from, - to: Some(to), - data: Some(data), - value: Some(value), - gas_limit: Some(gas_limit), - }) - } -} diff --git a/evm_loader/lib/src/types/mod.rs b/evm_loader/lib/src/types/mod.rs index 4604adc92..c7787274d 100644 --- a/evm_loader/lib/src/types/mod.rs +++ b/evm_loader/lib/src/types/mod.rs @@ -1,263 +1,491 @@ -mod indexer_db; -pub mod request_models; -#[allow(clippy::all)] -pub mod trace; -mod tracer_ch_db; - +pub mod tracer_ch_common; + +pub(crate) mod tracer_ch_db; +pub mod tracer_rocks_db; + +use crate::account_data::AccountData; +use crate::commands::get_config::ChainInfo; +use crate::config::DbConfig; +use crate::tracing::TraceCallConfig; +use crate::types::tracer_ch_common::{EthSyncStatus, RevisionMap}; +pub use crate::types::tracer_ch_db::ClickHouseDb; +pub use crate::types::tracer_rocks_db::RocksDb; +use async_trait::async_trait; +use enum_dispatch::enum_dispatch; +use ethnum::U256; +use evm_loader::solana_program::clock::{Slot, UnixTimestamp}; pub use evm_loader::types::Address; -pub use indexer_db::IndexerDb; -use lazy_static::lazy_static; -use solana_sdk::pubkey::Pubkey; -use std::str::FromStr; -use tokio::runtime::Runtime; -use tokio::task::block_in_place; -pub use tracer_ch_db::{ChError, ChResult, ClickHouseDb as TracerDb}; - -use { - crate::types::trace::{TraceCallConfig, TraceConfig}, - ethnum::U256, - hex::FromHex, - postgres::NoTls, - serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}, - std::{fmt, ops::Deref}, - thiserror::Error, - tokio_postgres::{connect, Client}, +use evm_loader::types::{StorageKey, Transaction}; +use evm_loader::{ + account_storage::AccountStorage, + types::{ + vector::VectorVecExt, vector::VectorVecSlowExt, AccessListTx, DynamicFeeTx, LegacyTx, + TransactionPayload, + }, }; - -/// Wrapper structure around vector of bytes. -#[derive(Debug, PartialEq, Eq, Default, Hash, Clone)] -pub struct Bytes(pub Vec); - -impl Bytes { - /// Simple constructor. - pub fn new(bytes: Vec) -> Bytes { - Bytes(bytes) - } +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use serde_with::{hex::Hex, serde_as, DisplayFromStr, OneOrMany}; +use solana_sdk::signature::Signature; +use solana_sdk::{account::Account, pubkey::Pubkey}; +use std::collections::HashMap; +use DbConfig::{ChDbConfig, RocksDbConfig}; + +pub type DbResult = Result; + +#[enum_dispatch] +pub enum TracerDb { + ClickHouseDb, + RocksDb, } -impl Deref for Bytes { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 +impl TracerDb { + pub async fn from_config(db_config: &DbConfig) -> Self { + match db_config { + RocksDbConfig(rocks_db_config) => RocksDb::new(rocks_db_config).await.into(), + ChDbConfig(ch_db_config) => ClickHouseDb::new(ch_db_config).into(), + } } -} -impl From> for Bytes { - fn from(bytes: Vec) -> Bytes { - Bytes(bytes) + pub async fn maybe_from_config(maybe_db_config: &Option) -> Option { + if let Some(db_config) = maybe_db_config { + Some(Self::from_config(db_config).await) + } else { + None + } } } -impl From for Vec { - fn from(value: Bytes) -> Self { - value.0 +impl Clone for TracerDb { + fn clone(&self) -> Self { + match self { + Self::RocksDb(r) => r.clone().into(), + Self::ClickHouseDb(c) => c.clone().into(), + } } } -impl Serialize for Bytes { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut value = "0x".to_owned(); - value.push_str(hex::encode(&self.0).as_str()); - serializer.serialize_str(value.as_ref()) - } -} +#[async_trait] +#[enum_dispatch(TracerDb)] +pub trait TracerDbTrait { + async fn get_block_time(&self, slot: Slot) -> DbResult; -impl<'a> Deserialize<'a> for Bytes { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - deserializer.deserialize_any(BytesVisitor) - } -} + async fn get_earliest_rooted_slot(&self) -> DbResult; -struct BytesVisitor; + async fn get_latest_block(&self) -> DbResult; -impl<'a> Visitor<'a> for BytesVisitor { - type Value = Bytes; + async fn get_account_at( + &self, + pubkey: &Pubkey, + slot: u64, + tx_index_in_block: Option, + ) -> DbResult>; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a 0x-prefixed, hex-encoded vector of bytes") - } + async fn get_transaction_index(&self, signature: Signature) -> DbResult; - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - if value.len() >= 2 && value.starts_with("0x") && value.len() & 1 == 0 { - Ok(Bytes::new(FromHex::from_hex(&value[2..]).map_err(|e| { - serde::de::Error::custom(format!("Invalid hex: {e}")) - })?)) - } else { - Err(serde::de::Error::custom( - "Invalid bytes format. Expected a 0x-prefixed hex string with even length", - )) - } - } + async fn get_neon_revisions(&self, _pubkey: &Pubkey) -> DbResult; - fn visit_string(self, value: String) -> Result - where - E: serde::de::Error, - { - self.visit_str(value.as_ref()) - } -} + async fn get_neon_revision(&self, _slot: Slot, _pubkey: &Pubkey) -> DbResult; + + async fn get_slot_by_blockhash(&self, blockhash: String) -> DbResult; -#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Clone, Default)] -pub struct ChDbConfig { - pub clickhouse_url: Vec, - pub clickhouse_user: Option, - pub clickhouse_password: Option, - pub indexer_host: String, - pub indexer_port: String, - pub indexer_database: String, - pub indexer_user: String, - pub indexer_password: String, + async fn get_sync_status(&self) -> DbResult; + + async fn get_accounts_in_transaction( + &self, + sol_sig: &[u8], + slot: u64, + ) -> DbResult>; } +#[serde_as] #[derive(Clone, Serialize, Deserialize, Debug)] +pub struct AccessListItem { + pub address: Address, + #[serde(rename = "storageKeys")] + #[serde_as(as = "Vec")] + pub storage_keys: Vec, +} + +#[serde_as] +#[skip_serializing_none] +#[derive(Clone, Serialize, Deserialize, Default)] pub struct TxParams { pub nonce: Option, pub from: Address, pub to: Option
, + #[serde_as(as = "Option")] pub data: Option>, pub value: Option, pub gas_limit: Option, + pub actual_gas_used: Option, + pub gas_price: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub access_list: Option>, + pub chain_id: Option, +} + +impl TxParams { + pub async fn into_transaction(self, backend: &impl AccountStorage) -> (Address, Transaction) { + let chain_id = self.chain_id.unwrap_or_else(|| backend.default_chain_id()); + + let origin_nonce = backend.nonce(self.from, chain_id).await; + let nonce = self.nonce.unwrap_or(origin_nonce); + + let payload = if let Some(access_list) = self.access_list { + let access_list: Vec<_> = access_list + .into_iter() + .map(|a| (a.address, a.storage_keys.into_vector())) + .collect(); + + if let Some(max_priority_fee_per_gas) = self.max_priority_fee_per_gas { + let dynamic_fee_tx = DynamicFeeTx { + nonce, + max_priority_fee_per_gas, + max_fee_per_gas: self.max_fee_per_gas.unwrap_or(max_priority_fee_per_gas * 2), + gas_limit: self.gas_limit.unwrap_or(U256::MAX), + target: self.to, + value: self.value.unwrap_or_default(), + call_data: self.data.unwrap_or_default().into_vector(), + chain_id: U256::from(chain_id), + access_list: access_list.elementwise_copy_into_vector(), + r: U256::ZERO, + s: U256::ZERO, + recovery_id: 0, + }; + TransactionPayload::DynamicFee(dynamic_fee_tx) + } else { + let access_list_tx = AccessListTx { + nonce, + gas_price: U256::ZERO, + gas_limit: self.gas_limit.unwrap_or(U256::MAX), + target: self.to, + value: self.value.unwrap_or_default(), + call_data: self.data.unwrap_or_default().into_vector(), + chain_id: U256::from(chain_id), + access_list: access_list.elementwise_copy_into_vector(), + r: U256::ZERO, + s: U256::ZERO, + recovery_id: 0, + }; + TransactionPayload::AccessList(access_list_tx) + } + } else { + let legacy_tx = LegacyTx { + nonce, + gas_price: U256::ZERO, + gas_limit: self.gas_limit.unwrap_or(U256::MAX), + target: self.to, + value: self.value.unwrap_or_default(), + call_data: self.data.unwrap_or_default().into_vector(), + chain_id: self.chain_id.map(U256::from), + v: U256::ZERO, + r: U256::ZERO, + s: U256::ZERO, + recovery_id: 0, + }; + TransactionPayload::Legacy(legacy_tx) + }; + + let tx = Transaction { + transaction: payload, + byte_len: 0, + hash: [0; 32], + signed_hash: [0; 32], + }; + + (self.from, tx) + } + + #[must_use] + pub fn from_transaction(origin: Address, tx: &Transaction) -> Self { + Self { + from: origin, + to: tx.target(), + nonce: Some(tx.nonce()), + data: Some(tx.call_data().to_vec()), + value: Some(tx.value()), + gas_limit: Some(tx.gas_limit()), + gas_price: Some(tx.gas_price()), + max_fee_per_gas: tx.max_fee_per_gas(), + max_priority_fee_per_gas: tx.max_priority_fee_per_gas(), + chain_id: tx.chain_id(), + access_list: None, + actual_gas_used: None, + } + } +} + +impl std::fmt::Debug for TxParams { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let json = serde_json::to_string(self).map_err(|_| std::fmt::Error)?; + + f.write_str(&json) + } +} + +#[serde_as] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct SerializedAccount { + pub lamports: u64, + #[serde_as(as = "DisplayFromStr")] + pub owner: Pubkey, + pub executable: bool, + pub rent_epoch: u64, + #[serde_as(as = "Hex")] + pub data: Vec, +} + +impl From<&SerializedAccount> for Account { + fn from(account: &SerializedAccount) -> Self { + Self { + lamports: account.lamports, + owner: account.owner, + executable: account.executable, + rent_epoch: account.rent_epoch, + data: account.data.clone(), + } + } } +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum AccountInfoLevel { + Changed, + All, +} + +#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TransactionParams { - pub data: Option, +pub struct EmulateRequest { + pub tx: TxParams, + pub step_limit: Option, + pub chains: Option>, pub trace_config: Option, + #[serde_as(as = "Vec")] + pub accounts: Vec, + #[serde_as(as = "Option>")] + pub solana_overrides: Option>>, + pub provide_account_info: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TransactionHashParams { - pub trace_config: Option, +pub struct EmulateApiRequest { + #[serde(flatten)] + pub body: EmulateRequest, + pub slot: Option, + pub tx_index_in_block: Option, + pub id: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TraceNextBlockParams { - pub trace_config: Option, +#[derive(Deserialize, Serialize, Debug, Default, Copy, Clone, Eq, PartialEq)] +pub struct BalanceAddress { + pub address: Address, + pub chain_id: u64, } -pub async fn do_connect( - host: &String, - port: &String, - db: &String, - user: &String, - pass: &String, -) -> Client { - let authority = format!("host={host} port={port} dbname={db} user={user} password={pass}"); - - let mut attempt = 0; - let mut result = None; - - while attempt < 3 { - result = connect(&authority, NoTls).await.ok(); - if result.is_some() { - break; - } - attempt += 1; +impl BalanceAddress { + #[must_use] + pub fn find_pubkey(&self, program_id: &Pubkey) -> Pubkey { + self.address + .find_balance_address(program_id, self.chain_id) + .0 } - let (client, connection) = result.expect("error to set DB connection"); + #[must_use] + pub fn find_contract_pubkey(&self, program_id: &Pubkey) -> Pubkey { + self.address.find_solana_address(program_id).0 + } +} - tokio::spawn(async move { - if let Err(e) = connection.await { - eprintln!("connection error: {e}"); - } - }); - client +#[serde_as] +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct GetBalanceRequest { + #[serde_as(as = "OneOrMany<_>")] + pub account: Vec, + pub slot: Option, + pub id: Option, } -lazy_static! { - pub static ref RT: Runtime = Runtime::new().unwrap(); +#[serde_as] +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct GetContractRequest { + #[serde_as(as = "OneOrMany<_>")] + pub contract: Vec
, + pub slot: Option, + pub id: Option, } -pub fn block(f: Fu) -> Fu::Output -where - Fu: std::future::Future, -{ - match tokio::runtime::Handle::try_current() { - Ok(handle) => block_in_place(|| handle.block_on(f)), - Err(_) => RT.block_on(f), - } +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct GetStorageAtRequest { + pub contract: Address, + pub index: U256, + pub slot: Option, + pub id: Option, } -#[derive(Error, Debug)] -pub enum PgError { - #[error("postgres: {}", .0)] - Db(#[from] tokio_postgres::Error), - #[error("Custom: {0}")] - Custom(String), +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct CancelTrxRequest { + pub storage_account: Pubkey, } -pub type PgResult = std::result::Result; +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct RequestWithSlot { + pub slot: Option, + pub tx_index_in_block: Option, +} -#[derive(Debug, Default, Clone, Copy)] -pub struct PubkeyBase58(pub Pubkey); +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct GetNeonElfRequest { + pub program_location: Option, +} -impl AsRef for PubkeyBase58 { - fn as_ref(&self) -> &Pubkey { - &self.0 - } +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct InitEnvironmentRequest { + pub send_trx: bool, + pub force: bool, + pub keys_dir: Option, + pub file: Option, } -impl From for PubkeyBase58 { - fn from(value: Pubkey) -> Self { - Self(value) - } +#[serde_as] +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct GetHolderRequest { + #[serde_as(as = "DisplayFromStr")] + pub pubkey: Pubkey, + pub slot: Option, + pub id: Option, } -impl From<&Pubkey> for PubkeyBase58 { - fn from(value: &Pubkey) -> Self { - Self(*value) - } +#[serde_as] +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct SimulateSolanaRequest { + pub compute_units: Option, + pub heap_size: Option, + pub account_limit: Option, + pub verify: Option, + #[serde_as(as = "Hex")] + pub blockhash: [u8; 32], + #[serde_as(as = "Vec")] + pub transactions: Vec>, + pub id: Option, } -impl From for Pubkey { - fn from(value: PubkeyBase58) -> Self { - value.0 +#[cfg(test)] +mod tests { + use crate::types::tracer_ch_common::RevisionMap; + + #[test] + fn test_build_ranges_empty() { + let results = Vec::new(); + let exp = Vec::new(); + let res = RevisionMap::build_ranges(&results); + assert_eq!(res, exp); } -} -impl Serialize for PubkeyBase58 { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let bs58 = bs58::encode(&self.0).into_string(); - serializer.serialize_str(&bs58) + #[test] + fn test_build_ranges_single_element() { + let results = vec![(1u64, String::from("Rev1"))]; + let exp = vec![(1u64, 2u64, String::from("Rev1"))]; + let res = RevisionMap::build_ranges(&results); + assert_eq!(res, exp); } -} -impl<'de> Deserialize<'de> for PubkeyBase58 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct StringVisitor; + #[test] + fn test_build_ranges_multiple_elements_different_revision() { + let results = vec![ + (222_222_222u64, String::from("Rev1")), + (333_333_333u64, String::from("Rev2")), + (444_444_444u64, String::from("Rev3")), + ]; + + let exp = vec![ + (222_222_222u64, 333_333_333u64, String::from("Rev1")), + (333_333_334u64, 444_444_444u64, String::from("Rev2")), + (444_444_445u64, 444_444_445u64, String::from("Rev3")), + ]; + let res = RevisionMap::build_ranges(&results); + + assert_eq!(res, exp); + } - impl<'de> serde::de::Visitor<'de> for StringVisitor { - type Value = Pubkey; + #[test] + fn test_rangemap() { + let ranges = vec![ + (123_456_780, 123_456_788, String::from("Rev1")), + (123_456_789, 123_456_793, String::from("Rev2")), + (123_456_794, 123_456_799, String::from("Rev3")), + ]; + let map = RevisionMap::new(ranges); - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a string containing json data") - } + assert_eq!(map.get(123_456_779), None); // Below the bottom bound of the first range - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - Pubkey::from_str(v).map_err(E::custom) - } + assert_eq!(map.get(123_456_780), Some(String::from("Rev1"))); // The bottom bound of the first range + assert_eq!(map.get(123_456_785), Some(String::from("Rev1"))); // Within the first range + assert_eq!(map.get(123_456_788), Some(String::from("Rev1"))); // The top bound of the first range + + assert_eq!(map.get(123_456_793), Some(String::from("Rev2"))); // The bottom bound of the second range + assert_eq!(map.get(123_456_790), Some(String::from("Rev2"))); // Within the second range + assert_eq!(map.get(123_456_793), Some(String::from("Rev2"))); // The top bound of the second range + + assert_eq!(map.get(123_456_799), Some(String::from("Rev3"))); // The bottom bound of the third range + assert_eq!(map.get(123_456_795), Some(String::from("Rev3"))); // Within the third range + assert_eq!(map.get(123_456_799), Some(String::from("Rev3"))); // The top bound of the third range + + assert_eq!(map.get(123_456_800), None); // Beyond the top end of the last range + } + + #[test] + fn test_deserialize() { + let txt = r#" + { + "step_limit": 500000, + "accounts": [], + "chains": [ + { + "id": 111, + "name": "neon", + "token": "HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU" + }, + { + "id": 112, + "name": "sol", + "token": "So11111111111111111111111111111111111111112" + }, + { + "id": 113, + "name": "usdt", + "token": "2duuuuhNJHUYqcnZ7LKfeufeeTBgSJdftf2zM3cZV6ym" + }, + { + "id": 114, + "name": "eth", + "token": "EwJYd3UAFAgzodVeHprB2gMQ68r4ZEbbvpoVzCZ1dGq5" + } + ], + "tx": { + "from": "0x3fd219e7cf0e701fcf5a6903b40d47ca4e597d99", + "to": "0x0673ac30e9c5dd7955ae9fb7e46b3cddca435883", + "value": "0x0", + "data": "3ff21f8e", + "chain_id": 111 + }, + "solana_overrides": { + "EwJYd3UAFAgzodVeHprB2gMQ68r4ZEbbvpoVzCZ1dGq5": null, + "2duuuuhNJHUYqcnZ7LKfeufeeTBgSJdftf2zM3cZV6ym": { + "lamports": 1000000000000, + "owner": "So11111111111111111111111111111111111111112", + "executable": false, + "rent_epoch": 0, + "data": "0102030405" + } + }, + "provide_account_info": null } + "#; - deserializer.deserialize_any(StringVisitor).map(Self) + let request: super::EmulateRequest = serde_json::from_str(txt).unwrap(); + println!("{request:?}"); } } diff --git a/evm_loader/lib/src/types/request_models.rs b/evm_loader/lib/src/types/request_models.rs deleted file mode 100644 index e64cda0e1..000000000 --- a/evm_loader/lib/src/types/request_models.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::types::trace::{TraceCallConfig, TraceConfig}; -use crate::types::{PubkeyBase58, TxParams}; -use ethnum::U256; -use evm_loader::types::Address; -use serde::{Deserialize, Serialize}; -use solana_sdk::pubkey::Pubkey; - -#[derive(Deserialize, Serialize, Debug, Default)] -pub struct GetEtherRequest { - pub ether: Address, - pub slot: Option, -} - -#[derive(Deserialize, Serialize, Debug, Default)] -pub struct GetStorageAtRequest { - pub contract_id: Address, - pub index: U256, - pub slot: Option, -} - -#[derive(Deserialize, Serialize, Debug, Default)] -pub struct TxParamsRequestModel { - pub sender: Address, - pub contract: Option
, - pub data: Option>, - pub value: Option, - pub gas_limit: Option, -} - -impl From for TxParams { - fn from(model: TxParamsRequestModel) -> Self { - Self { - nonce: None, - from: model.sender, - to: model.contract, - data: model.data, - value: model.value, - gas_limit: model.gas_limit, - } - } -} - -#[derive(Deserialize, Serialize, Debug, Default)] -pub struct EmulationParamsRequestModel { - pub token_mint: Option, - pub chain_id: Option, - pub max_steps_to_execute: u64, - pub cached_accounts: Option>, - pub solana_accounts: Option>, -} - -impl EmulationParamsRequestModel { - #[allow(unused)] - pub fn new( - token_mint: Option, - chain_id: Option, - max_steps_to_execute: u64, - cached_accounts: Option>, - solana_accounts: Option>, - ) -> EmulationParamsRequestModel { - let token_mint = token_mint.map(Into::into); - let solana_accounts = solana_accounts.map(|vec| vec.into_iter().map(Into::into).collect()); - - Self { - token_mint, - chain_id, - max_steps_to_execute, - cached_accounts, - solana_accounts, - } - } -} - -#[derive(Deserialize, Serialize, Debug, Default)] -pub struct EmulateRequestModel { - #[serde(flatten)] - pub tx_params: TxParamsRequestModel, - #[serde(flatten)] - pub emulation_params: EmulationParamsRequestModel, - pub slot: Option, -} - -#[derive(Deserialize, Serialize, Debug, Default)] -pub struct EmulateHashRequestModel { - #[serde(flatten)] - pub emulation_params: EmulationParamsRequestModel, - pub hash: String, -} - -#[derive(Deserialize, Serialize, Debug, Default)] -pub struct TraceRequestModel { - #[serde(flatten)] - pub emulate_request: EmulateRequestModel, - pub trace_call_config: Option, -} - -#[derive(Deserialize, Serialize, Debug, Default)] -pub struct TraceHashRequestModel { - #[serde(flatten)] - pub emulate_hash_request: EmulateHashRequestModel, - pub trace_config: Option, -} - -#[derive(Deserialize, Serialize, Debug, Default)] -pub struct TraceNextBlockRequestModel { - #[serde(flatten)] - pub emulation_params: EmulationParamsRequestModel, - pub slot: u64, - pub trace_config: Option, -} diff --git a/evm_loader/lib/src/types/trace.rs b/evm_loader/lib/src/types/trace.rs deleted file mode 100644 index 854d69e8a..000000000 --- a/evm_loader/lib/src/types/trace.rs +++ /dev/null @@ -1,302 +0,0 @@ -use evm_loader::{account::EthereumAccount, types::Address}; -use serde::{Deserialize, Serialize}; -use std::fmt::{Display, Formatter}; -use {crate::types::Bytes, ethnum::U256, std::collections::HashMap}; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq /*, RlpEncodable, RlpDecodable */)] -/// A diff of some chunk of memory. -pub struct MemoryDiff { - /// Offset into memory the change begins. - pub offset: usize, - /// The changed data. - pub data: Bytes, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq /*, RlpEncodable, RlpDecodable */)] -/// A diff of some storage value. -pub struct StorageDiff { - /// Which key in storage is changed. - pub location: U256, - /// What the value has been changed to. - pub value: [u8; 32], -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq /*, RlpEncodable, RlpDecodable */)] -/// A record of an executed VM operation. -pub struct VMExecutedOperation { - /// The total gas used. - pub gas_used: U256, - /// The stack item placed, if any. - pub stack_push: Vec<[u8; 32]>, - /// If altered, the memory delta. - pub mem_diff: Option, - /// The altered storage value, if any. - pub store_diff: Option, -} - -#[derive( - Serialize, Deserialize, Debug, Clone, PartialEq, Default /*, RlpEncodable, RlpDecodable */, -)] -/// A record of the execution of a single VM operation. -pub struct VMOperation { - /// The program counter. - pub pc: usize, - /// The instruction executed. - pub instruction: u8, - /// The gas cost for this instruction. - pub gas_cost: U256, - /// Information concerning the execution of the operation. - pub executed: Option, -} - -#[derive( - Serialize, Deserialize, Debug, Clone, PartialEq, Default /*, RlpEncodable, RlpDecodable */, -)] -/// A record of a full VM trace for a CALL/CREATE. -#[allow(clippy::module_name_repetitions)] -pub struct VMTrace { - /// The step (i.e. index into operations) at which this trace corresponds. - pub parent_step: usize, - /// The code to be executed. - pub code: Bytes, - /// The operations executed. - pub operations: Vec, - /// The sub traces for each interior action performed as part of this call/create. - /// Thre is a 1:1 correspondance between these and a CALL/CREATE/CALLCODE/DELEGATECALL instruction. - pub subs: Vec, -} - -// OpenEthereum tracer ethcore/trace/src/executive_tracer.rs -#[allow(clippy::module_name_repetitions)] -pub struct TraceData { - pub mem_written: Option<(usize, usize)>, - pub store_written: Option<(U256, [u8; 32])>, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct FullTraceData { - pub stack: Vec<[u8; 32]>, - pub memory: Vec, - pub storage: HashMap, - pub return_data: Option>, -} - -/// Simple VM tracer. Traces all operations. -pub struct ExecutiveVMTracer { - data: VMTrace, - pub depth: usize, - trace_stack: Vec, -} - -impl ExecutiveVMTracer { - /// Create a new top-level instance. - pub fn toplevel() -> Self { - ExecutiveVMTracer { - data: VMTrace { - parent_step: 0, - code: Bytes::default(), - operations: vec![VMOperation::default()], // prefill with a single entry so that prepare_subtrace can get the parent_step - subs: vec![], - }, - depth: 0, - trace_stack: vec![], - } - } - - fn with_trace_in_depth(trace: &mut VMTrace, depth: usize, f: F) { - if depth == 0 { - f(trace); - } else { - Self::with_trace_in_depth(trace.subs.last_mut().expect("self.depth is incremented with prepare_subtrace; a subtrace is always pushed; self.depth cannot be greater than subtrace stack; qed"), depth - 1, f); - } - } -} - -impl VMTracer for ExecutiveVMTracer { - type Output = VMTrace; - - fn trace_prepare_execute(&mut self, pc: usize, instruction: u8) { - Self::with_trace_in_depth(&mut self.data, self.depth, move |trace| { - trace.operations.push(VMOperation { - pc, - instruction, - gas_cost: U256::ZERO, - executed: None, - }); - }); - } - - fn trace_executed( - &mut self, - gas_used: U256, - stack_push: Vec<[u8; 32]>, - mem_diff: Option, - store_diff: Option, - ) { - self.trace_stack.push(TraceData { - mem_written: mem_diff.as_ref().map(|d| (d.offset, d.data.len())), - store_written: store_diff.as_ref().map(|d| (d.location, d.value)), - }); - - Self::with_trace_in_depth(&mut self.data, self.depth, move |trace| { - let operation = trace.operations.last_mut().expect("trace_executed is always called after a trace_prepare_execute; trace.operations cannot be empty; qed"); - operation.executed = Some(VMExecutedOperation { - gas_used, - stack_push, - mem_diff, - store_diff, - }); - }); - } - - fn prepare_subtrace(&mut self, code: Vec) { - Self::with_trace_in_depth(&mut self.data, self.depth, move |trace| { - let parent_step = trace.operations.len() - 1; // won't overflow since we must already have pushed an operation in trace_prepare_execute. - trace.subs.push(VMTrace { - parent_step, - code: code.into(), - operations: vec![], - subs: vec![], - }); - }); - self.depth += 1; - } - - fn done_subtrace(&mut self) { - self.depth -= 1; - } - - fn drain(mut self) -> Option { - self.data.subs.pop() - } -} - -// ethcore/src/trace/mod.rs -pub trait VMTracer: Send { - /// Data returned when draining the `VMTracer`. - type Output; - - /// Trace the preparation to execute a single valid instruction. - fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8) {} - - /// Trace the finalised execution of a single valid instruction. - fn trace_executed( - &mut self, - _gas_used: U256, - _stack_push: Vec<[u8; 32]>, - _mem_diff: Option, - _storage_diff: Option, - ) { - } - - /// Spawn subtracer which will be used to trace deeper levels of execution. - fn prepare_subtrace(&mut self, _code: Vec) {} - - /// Finalize subtracer. - fn done_subtrace(&mut self) {} - - /// Consumes self and returns the VM trace. - fn drain(self) -> Option; -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct TracedCall { - pub vm_trace: Option, - pub full_trace_data: Vec, - pub used_gas: u64, - pub result: Vec, - pub exit_status: String, -} - -impl Display for TracedCall { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{{ exit_status: {}, used_gas: {}, vm_trace: {}, full_trace_data: {}, result: {} }}", - self.exit_status, - self.used_gas, - if self.vm_trace.is_some() { "yes" } else { "no" }, - self.full_trace_data.len(), - hex::encode(&self.result), - ) - } -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct BlockOverrides { - pub number: Option, - #[allow(unused)] - pub difficulty: Option, // NOT SUPPORTED by Neon EVM - pub time: Option, - #[allow(unused)] - pub gas_limit: Option, // NOT SUPPORTED BY Neon EVM - #[allow(unused)] - pub coinbase: Option
, // NOT SUPPORTED BY Neon EVM - #[allow(unused)] - pub random: Option, // NOT SUPPORTED BY Neon EVM - #[allow(unused)] - pub base_fee: Option, // NOT SUPPORTED BY Neon EVM -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AccountOverride { - pub nonce: Option, - pub code: Option, - pub balance: Option, - pub state: Option>, - pub state_diff: Option>, -} - -impl AccountOverride { - pub fn apply(&self, ether_account: &mut EthereumAccount) { - if let Some(nonce) = self.nonce { - ether_account.trx_count = nonce; - } - if let Some(balance) = self.balance { - ether_account.balance = U256::from(balance); - } - #[allow(clippy::cast_possible_truncation)] - if let Some(code) = &self.code { - ether_account.code_size = code.len() as u32; - } - } -} - -pub type AccountOverrides = HashMap; - -#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -#[allow(clippy::module_name_repetitions, clippy::struct_excessive_bools)] -pub struct TraceConfig { - #[serde(default)] - pub enable_memory: bool, - #[serde(default)] - pub disable_storage: bool, - #[serde(default)] - pub disable_stack: bool, - #[serde(default)] - pub enable_return_data: bool, - pub tracer: Option, - pub timeout: Option, -} - -#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -#[allow(clippy::module_name_repetitions)] -pub struct TraceCallConfig { - #[serde(flatten)] - pub trace_config: TraceConfig, - pub block_overrides: Option, - pub state_overrides: Option, -} - -impl From for TraceCallConfig { - fn from(trace_config: TraceConfig) -> Self { - Self { - trace_config, - ..Self::default() - } - } -} diff --git a/evm_loader/lib/src/types/tracer_ch_common.rs b/evm_loader/lib/src/types/tracer_ch_common.rs new file mode 100644 index 000000000..a38da0712 --- /dev/null +++ b/evm_loader/lib/src/types/tracer_ch_common.rs @@ -0,0 +1,231 @@ +use std::fmt; + +use clickhouse::Row; +use evm_loader::solana_program::debug_account_data::debug_account_data; +use serde::{Deserialize, Serialize}; +use solana_sdk::{account::Account, pubkey::Pubkey}; +use std::collections::BTreeMap; +use std::time::Instant; +use thiserror::Error; + +use crate::account_data::AccountData; + +pub const ROOT_BLOCK_DELAY: u8 = 100; + +#[derive(Error, Debug)] +pub enum ChError { + #[error("clickhouse: {}", .0)] + Db(#[from] clickhouse::error::Error), +} + +pub type ChResult = std::result::Result; + +pub enum SlotStatus { + #[allow(unused)] + Confirmed = 1, + #[allow(unused)] + Processed = 2, + Rooted = 3, +} + +#[derive(Debug, Row, serde::Deserialize, Clone)] +pub struct SlotParent { + pub slot: u64, + pub parent: Option, + pub status: u8, +} + +#[derive(Debug, Row, serde::Deserialize, Clone)] +pub struct SlotParentRooted { + pub slot: u64, + pub parent: Option, +} + +impl From for SlotParent { + fn from(slot_parent_rooted: SlotParentRooted) -> Self { + Self { + slot: slot_parent_rooted.slot, + parent: slot_parent_rooted.parent, + status: SlotStatus::Rooted as u8, + } + } +} + +impl SlotParent { + #[must_use] + pub const fn is_rooted(&self) -> bool { + self.status == SlotStatus::Rooted as u8 + } +} + +// NEON_REVISION row +#[derive(Row, Deserialize)] +pub struct RevisionRow { + pub slot: u64, + pub data: Vec, +} + +#[derive(Row, serde::Deserialize, Clone)] +pub struct AccountRow { + pub pubkey: Vec, + pub owner: Vec, + pub lamports: u64, + pub executable: bool, + pub rent_epoch: u64, + pub data: Vec, + pub txn_signature: Vec>, +} + +impl fmt::Debug for AccountRow { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug_struct = f.debug_struct("AccountRow"); + + debug_struct + .field("pubkey", &bs58::encode(&self.pubkey).into_string()) + .field("owner", &bs58::encode(&self.owner).into_string()) + .field("lamports", &self.lamports) + .field("executable", &self.executable) + .field("rent_epoch", &self.rent_epoch) + .field("data_len", &self.data.len()); + + debug_account_data(&self.data, &mut debug_struct); + + debug_struct.field( + "txn_signature", + &bs58::encode( + self.txn_signature + .iter() + .filter_map(|option| *option) + .collect::>(), + ) + .into_string(), + ); + + debug_struct.finish() + } +} + +fn pubkey_from(src: Vec) -> Result { + Pubkey::try_from(src).map_err(|src| { + format!( + "Incorrect slice length ({}) while converting owner from: {src:?}", + src.len(), + ) + }) +} + +impl TryInto for AccountRow { + type Error = String; + + fn try_into(self) -> Result { + let owner = pubkey_from(self.owner)?; + + Ok(Account { + lamports: self.lamports, + data: self.data, + owner, + rent_epoch: self.rent_epoch, + executable: self.executable, + }) + } +} + +impl TryInto for AccountRow { + type Error = String; + + fn try_into(self) -> Result { + let owner = pubkey_from(self.owner)?; + let pubkey = pubkey_from(self.pubkey)?; + + Ok(AccountData::new_from_account( + pubkey, + &Account { + lamports: self.lamports, + data: self.data, + owner, + rent_epoch: self.rent_epoch, + executable: self.executable, + }, + )) + } +} + +pub enum EthSyncStatus { + Syncing(EthSyncing), + Synced, +} + +impl EthSyncStatus { + #[must_use] + pub fn new(syncing_status: Option) -> Self { + syncing_status.map_or(Self::Synced, Self::Syncing) + } +} + +#[derive(Row, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EthSyncing { + pub starting_block: u64, + pub current_block: u64, + pub highest_block: u64, +} + +pub struct RevisionMap { + map: BTreeMap, + pub last_update: Instant, +} + +impl RevisionMap { + #[must_use] + pub fn new(neon_revision_ranges: Vec<(u64, u64, String)>) -> Self { + let mut map = BTreeMap::new(); + + for (start, end, value) in neon_revision_ranges { + map.insert(start, value.clone()); + map.insert(end, value); + } + + let last_update = std::time::Instant::now(); + + Self { map, last_update } + } + + // When deploying a program for the first time it is now only available in the next slot (the slot after the one the deployment transaction landed in). + // When undeploying / closing a program the change is visible immediately and the very next instruction even within the transaction can not access it anymore. + // When redeploying the program becomes temporarily closed immediately and will reopen with the new version in the next slot. + #[must_use] + pub fn build_ranges(input: &[(u64, String)]) -> Vec<(u64, u64, String)> { + let mut ranges = Vec::new(); + + for i in 0..input.len() { + let (start, rev) = input[i].clone(); + let end = if i < input.len() - 1 { + input[i + 1].0 - 1 + } else { + start + }; + + match i { + 0 => ranges.push((start, end + 1, rev.clone())), + _ if i == input.len() - 1 => ranges.push((start + 1, end + 1, rev.clone())), + _ => ranges.push((start + 1, end + 1, rev.clone())), + } + } + ranges + } + #[must_use] + pub fn get(&self, slot: u64) -> Option { + // Check if slot is less than the starting range or + // greater than the ending range + let (start, _) = self.map.iter().next()?; + let (end, _) = self.map.iter().last()?; + + if slot < *start || slot > *end { + return None; + } + + let value = self.map.range(..=slot).next_back(); + + value.map(|(_, v)| v.clone()) + } +} diff --git a/evm_loader/lib/src/types/tracer_ch_db.rs b/evm_loader/lib/src/types/tracer_ch_db.rs index d3596d561..15dddd982 100644 --- a/evm_loader/lib/src/types/tracer_ch_db.rs +++ b/evm_loader/lib/src/types/tracer_ch_db.rs @@ -1,7 +1,19 @@ -use super::ChDbConfig; -use clickhouse::{Client, Row}; -use log::{debug, info}; +use crate::{ + commands::get_neon_elf::get_elf_parameter, + types::tracer_ch_common::{AccountRow, ChError, RevisionRow, SlotParent, ROOT_BLOCK_DELAY}, + types::{DbResult, TracerDbTrait}, +}; + +use super::tracer_ch_common::{ChResult, EthSyncStatus, EthSyncing, RevisionMap, SlotParentRooted}; + +use crate::account_data::AccountData; +use crate::config::ChDbConfig; +use anyhow::anyhow; +use async_trait::async_trait; +use clickhouse::Client; +use log::{debug, error, info}; use rand::Rng; +use solana_sdk::signature::Signature; use solana_sdk::{ account::Account, clock::{Slot, UnixTimestamp}, @@ -12,102 +24,18 @@ use std::{ Ord, Ordering::{Equal, Greater, Less}, }, - convert::TryFrom, - sync::Arc, time::Instant, }; -use thiserror::Error; - -const ROOT_BLOCK_DELAY: u8 = 100; - -#[derive(Error, Debug)] -pub enum ChError { - #[error("clickhouse: {}", .0)] - Db(#[from] clickhouse::error::Error), -} - -pub type ChResult = std::result::Result; -#[allow(dead_code)] #[derive(Clone)] pub struct ClickHouseDb { - pub client: Arc, -} - -pub enum SlotStatus { - #[allow(unused)] - Confirmed = 1, - #[allow(unused)] - Processed = 2, - Rooted = 3, -} - -#[derive(Debug, Row, serde::Deserialize, Clone)] -pub struct SlotParent { - pub slot: u64, - pub parent: Option, - pub status: u8, -} - -impl SlotParent { - fn is_rooted(&self) -> bool { - self.status == SlotStatus::Rooted as u8 - } -} - -#[derive(Debug, Row, serde::Deserialize, Clone)] -pub struct AccountRow { - owner: Vec, - lamports: u64, - executable: bool, - rent_epoch: u64, - data: Vec, - txn_signature: Vec>, + pub client: Client, } -impl TryInto for AccountRow { - type Error = String; - - fn try_into(self) -> Result { - let owner = Pubkey::try_from(self.owner).map_err(|src| { - format!( - "Incorrect slice length ({}) while converting owner from: {src:?}", - src.len(), - ) - })?; - - Ok(Account { - lamports: self.lamports, - data: self.data, - owner, - rent_epoch: self.rent_epoch, - executable: self.executable, - }) - } -} - -#[allow(dead_code)] -impl ClickHouseDb { - pub fn new(config: &ChDbConfig) -> Self { - let url_id = rand::thread_rng().gen_range(0..config.clickhouse_url.len()); - let url = config.clickhouse_url.get(url_id).unwrap(); - - let client = match (&config.clickhouse_user, &config.clickhouse_password) { - (None, None | Some(_)) => Client::default().with_url(url), - (Some(user), None) => Client::default().with_url(url).with_user(user), - (Some(user), Some(password)) => Client::default() - .with_url(url) - .with_user(user) - .with_password(password), - }; - - ClickHouseDb { - client: Arc::new(client), - } - } - - // return value is not used for tracer methods - pub async fn get_block_time(&self, slot: Slot) -> ChResult { +#[async_trait] +impl TracerDbTrait for ClickHouseDb { + // Returned value is not used for tracer methods. + async fn get_block_time(&self, slot: Slot) -> DbResult { let time_start = Instant::now(); let query = "SELECT JSONExtractInt(notify_block_json, 'block_time') FROM events.notify_block_distributed WHERE slot = ? LIMIT 1"; @@ -126,7 +54,24 @@ impl ClickHouseDb { result } - pub async fn get_latest_block(&self) -> ChResult { + async fn get_earliest_rooted_slot(&self) -> DbResult { + let time_start = Instant::now(); + let query = "SELECT min(slot) FROM events.rooted_slots"; + let result = self + .client + .query(query) + .fetch_one::() + .await + .map_err(std::convert::Into::into); + let execution_time = Instant::now().duration_since(time_start); + info!( + "get_earliest_rooted_slot sql returned {result:?}, time: {} sec", + execution_time.as_secs_f64() + ); + result + } + + async fn get_latest_block(&self) -> DbResult { let time_start = Instant::now(); let query = "SELECT max(slot) FROM events.update_slot"; let result = self @@ -143,6 +88,240 @@ impl ClickHouseDb { result } + async fn get_account_at( + &self, + pubkey: &Pubkey, + slot: u64, + tx_index_in_block: Option, + ) -> DbResult> { + if let Some(tx_index_in_block) = tx_index_in_block { + return if let Some(account) = self + .get_account_at_index_in_block(pubkey, slot, tx_index_in_block) + .await? + { + Ok(Some(account)) + } else { + self.get_account_at_slot(pubkey, slot - 1) + .await + .map_err(|e| anyhow!("Failed to get NEON_REVISION, error: {e}")) + }; + } + + self.get_account_at_slot(pubkey, slot) + .await + .map_err(|e| anyhow!("Failed to get NEON_REVISION, error: {e}")) + } + + async fn get_transaction_index(&self, signature: Signature) -> DbResult { + let query = r" + SELECT idx + FROM events.notify_transaction_distributed + WHERE signature = ? + LIMIT 1 + "; + self.client + .query(query) + .bind(format!("{:?}", signature.as_ref())) + .fetch_one::() + .await + .map_err(Into::into) + } + + async fn get_neon_revisions(&self, pubkey: &Pubkey) -> DbResult { + let query = r"SELECT slot, data + FROM events.update_account_distributed + WHERE + pubkey = ? + ORDER BY + slot ASC, + write_version ASC"; + + let pubkey_str = format!("{:?}", pubkey.to_bytes()); + let rows: Vec = self + .client + .query(query) + .bind(pubkey_str) + .fetch_all() + .await?; + + let mut results: Vec<(u64, String)> = Vec::new(); + + for row in rows { + let neon_revision = get_elf_parameter(&row.data, "NEON_REVISION").map_err(|e| { + anyhow!(ChError::Db(clickhouse::error::Error::Custom(format!( + "Failed to get NEON_REVISION, error: {e}", + )))) + })?; + results.push((row.slot, neon_revision)); + } + let ranges = RevisionMap::build_ranges(&results); + + Ok(RevisionMap::new(ranges)) + } + + async fn get_neon_revision(&self, slot: Slot, pubkey: &Pubkey) -> DbResult { + let query = r"SELECT data + FROM events.update_account_distributed + WHERE + pubkey = ? AND slot <= ? + ORDER BY + pubkey ASC, + slot ASC, + write_version ASC + LIMIT 1 + "; + + let pubkey_str = format!("{:?}", pubkey.to_bytes()); + + let data = Self::row_opt( + self.client + .query(query) + .bind(pubkey_str) + .bind(slot) + .fetch_one::>() + .await, + )?; + if let Some(data) = data { + let neon_revision = + get_elf_parameter(data.as_slice(), "NEON_REVISION").map_err(|e| { + ChError::Db(clickhouse::error::Error::Custom(format!( + "Failed to get NEON_REVISION, error: {e:?}", + ))) + })?; + Ok(neon_revision) + } else { + let err = clickhouse::error::Error::Custom(format!( + "get_neon_revision: for slot {slot} and pubkey {pubkey} not found", + )); + Err(anyhow!(ChError::Db(err))) + } + } + + async fn get_slot_by_blockhash(&self, blockhash: String) -> DbResult { + let query = r"SELECT slot + FROM events.notify_block_distributed + WHERE hash = ? + LIMIT 1 + "; + + let slot = Self::row_opt( + self.client + .query(query) + .bind(blockhash) + .fetch_one::() + .await, + )?; + slot.map_or_else( + || { + Err(anyhow!(ChError::Db(clickhouse::error::Error::Custom( + "get_slot_by_blockhash: no data available".to_string(), + )))) + }, + Ok, + ) + } + + async fn get_sync_status(&self) -> DbResult { + let query_is_startup = r"SELECT is_startup + FROM events.update_account_distributed + WHERE slot = ( + SELECT MAX(slot) + FROM events.update_account_distributed + ) + LIMIT 1 + "; + + let is_startup = Self::row_opt( + self.client + .query(query_is_startup) + .fetch_one::() + .await, + )?; + + if is_startup == Some(true) { + let query = r"SELECT slot + FROM ( + (SELECT MIN(slot) as slot FROM events.notify_block_distributed) + UNION ALL + (SELECT MAX(slot) as slot FROM events.notify_block_distributed) + UNION ALL + (SELECT MAX(slot) as slot FROM events.notify_block_distributed) + ) + ORDER BY slot ASC + "; + + let data = Self::row_opt(self.client.query(query).fetch_one::().await)?; + + return data.map_or_else( + || { + Err(anyhow!(ChError::Db(clickhouse::error::Error::Custom( + "get_sync_status: no data available".to_string(), + )))) + }, + |data| Ok(EthSyncStatus::new(Some(data))), + ); + } + + Ok(EthSyncStatus::new(None)) + } + + async fn get_accounts_in_transaction( + &self, + sol_sig: &[u8], + slot: u64, + ) -> DbResult> { + info!("get_accounts_in_transaction {{signature: {sol_sig:?} }}"); + + let query = r" + SELECT DISTINCT ON (pubkey) + pubkey, owner, lamports, executable, rent_epoch, data, txn_signature + FROM events.update_account_distributed + WHERE txn_signature = ? + AND slot = ? + ORDER BY write_version DESC + "; + + let rows = self + .client + .query(query) + .bind(sol_sig) + .bind(slot) + .fetch_all::() + .await?; + + let mut accounts: Vec = Vec::new(); + + for row in rows { + let account_data = row.try_into().map_err(|e| { + anyhow!(ChError::Db(clickhouse::error::Error::Custom(format!( + "get_accounts_in_transaction: Failed to convert row to AccountData, error: {e}", + )))) + })?; + accounts.push(account_data); + } + + Ok(accounts) + } +} + +impl ClickHouseDb { + #[must_use] + pub fn new(config: &ChDbConfig) -> Self { + let url_id = rand::thread_rng().gen_range(0..config.clickhouse_url.len()); + let url = config.clickhouse_url.get(url_id).unwrap(); + + let client = match (&config.clickhouse_user, &config.clickhouse_password) { + (None, None | Some(_)) => Client::default().with_url(url), + (Some(user), None) => Client::default().with_url(url).with_user(user), + (Some(user), Some(password)) => Client::default() + .with_url(url) + .with_user(user) + .with_password(password), + }; + + Self { client } + } + async fn get_branch_slots(&self, slot: Option) -> ChResult<(u64, Vec)> { fn branch_from( rows: Vec, @@ -166,19 +345,18 @@ impl ClickHouseDb { info!("get_branch_slots {{ slot: {slot:?} }}"); - let query = r#" + let query = r" SELECT DISTINCT ON (slot, parent) slot, parent, status FROM events.update_slot WHERE slot >= ( - SELECT slot - ? - FROM events.update_slot - WHERE status = 'Rooted' - ORDER BY slot DESC - LIMIT 1 - ) - AND isNotNull(parent) + SELECT slot - ? + FROM events.rooted_slots + ORDER BY slot DESC + LIMIT 1 + ) + AND isNotNull(parent) ORDER BY slot DESC, status DESC - "#; + "; let time_start = Instant::now(); let mut rows = self .client @@ -187,9 +365,7 @@ impl ClickHouseDb { .fetch_all::() .await?; - let first = if let Some(first) = rows.pop() { - first - } else { + let Some(first) = rows.pop() else { let err = clickhouse::error::Error::Custom("Rooted slot not found".to_string()); return Err(ChError::Db(err)); }; @@ -229,19 +405,20 @@ impl ClickHouseDb { async fn get_account_rooted_slot(&self, key: &str, slot: u64) -> ChResult> { info!("get_account_rooted_slot {{ key: {key}, slot: {slot} }}"); - let query = r#" - SELECT DISTINCT slot - FROM events.update_account_distributed - WHERE pubkey = ? - AND slot <= ? - AND slot IN ( - SELECT slot - FROM events.update_slot - WHERE status = 'Rooted' - ) - ORDER BY slot DESC - LIMIT 1 - "#; + + let query = r" + SELECT DISTINCT uad.slot + FROM events.update_account_distributed AS uad + WHERE uad.pubkey = ? + AND uad.slot <= ? + AND ( + SELECT COUNT(slot) + FROM events.rooted_slots + WHERE slot = ? + ) >= 1 + ORDER BY uad.slot DESC + LIMIT 1 + "; let time_start = Instant::now(); let slot_opt = Self::row_opt( @@ -249,6 +426,7 @@ impl ClickHouseDb { .query(query) .bind(key) .bind(slot) + .bind(slot) .fetch_one::() .await, )?; @@ -262,11 +440,14 @@ impl ClickHouseDb { Ok(slot_opt) } - #[allow(clippy::too_many_lines)] - pub async fn get_account_at(&self, pubkey: &Pubkey, slot: u64) -> ChResult> { - info!("get_account_at {{ pubkey: {pubkey}, slot: {slot} }}"); + async fn get_account_at_slot( + &self, + pubkey: &Pubkey, + slot: u64, + ) -> Result, ChError> { + info!("get_account_at_slot {{ pubkey: {pubkey}, slot: {slot} }}"); let (first, mut branch) = self.get_branch_slots(Some(slot)).await.map_err(|e| { - println!("get_branch_slots error: {:?}", e); + error!("get_branch_slots error: {:?}", e); e })?; @@ -276,7 +457,7 @@ impl ClickHouseDb { .get_account_rooted_slot(&pubkey_str, first) .await .map_err(|e| { - println!("get_account_rooted_slot error: {:?}", e); + error!("get_account_rooted_slot error: {:?}", e); e })? { @@ -286,31 +467,31 @@ impl ClickHouseDb { let mut row = if branch.is_empty() { None } else { - let query = r#" - SELECT owner, lamports, executable, rent_epoch, data, txn_signature + let query = r" + SELECT pubkey, owner, lamports, executable, rent_epoch, data, txn_signature FROM events.update_account_distributed WHERE pubkey = ? AND slot IN ? ORDER BY pubkey, slot DESC, write_version DESC LIMIT 1 - "#; + "; let time_start = Instant::now(); let row = Self::row_opt( self.client .query(query) .bind(pubkey_str.clone()) - .bind(&branch.as_slice()) + .bind(branch.as_slice()) .fetch_one::() .await, ) .map_err(|e| { - println!("get_account_at error: {e}"); + error!("get_account_at_slot error: {e}"); ChError::Db(e) })?; let execution_time = Instant::now().duration_since(time_start); info!( - "get_account_at {{ pubkey: {pubkey}, slot: {slot} }} sql(1) returned {row:?}, time: {} sec", + "get_account_at_slot {{ pubkey: {pubkey}, slot: {slot} }} sql(1) returned {row:?}, time: {} sec", execution_time.as_secs_f64() ); @@ -327,31 +508,76 @@ impl ClickHouseDb { ); } - let result = if let Some(acc) = row { - acc.try_into() - .map(Some) - .map_err(|err| ChError::Db(clickhouse::error::Error::Custom(err))) - } else { - Ok(None) - }; + let result = row + .map(std::convert::TryInto::try_into) + .transpose() + .map_err(|e| ChError::Db(clickhouse::error::Error::Custom(e))); - info!("get_account_at {{ pubkey: {pubkey}, slot: {slot} }} -> {result:?}"); + info!("get_account_at_slot {{ pubkey: {pubkey}, slot: {slot} }} -> {result:?}"); result } + async fn get_account_at_index_in_block( + &self, + pubkey: &Pubkey, + slot: u64, + tx_index_in_block: u64, + ) -> ChResult> { + info!( + "get_account_at_index_in_block {{ pubkey: {pubkey}, slot: {slot}, tx_index_in_block: {tx_index_in_block} }}" + ); + + let query = r" + SELECT pubkey, owner, lamports, executable, rent_epoch, data, txn_signature + FROM events.update_account_distributed + WHERE pubkey = ? + AND slot = ? + AND write_version <= ? + ORDER BY write_version DESC + LIMIT 1 + "; + + let time_start = Instant::now(); + + let account = Self::row_opt( + self.client + .query(query) + .bind(format!("{:?}", pubkey.to_bytes())) + .bind(slot) + .bind(tx_index_in_block) + .fetch_one::() + .await, + ) + .map_err(|e| { + error!("get_account_at_index_in_block error: {e}"); + ChError::Db(e) + })? + .map(std::convert::TryInto::try_into) + .transpose() + .map_err(|e| ChError::Db(clickhouse::error::Error::Custom(e)))?; + + let execution_time = Instant::now().duration_since(time_start); + info!( + "get_account_at_index_in_block {{ pubkey: {pubkey}, slot: {slot}, tx_index_in_block: {tx_index_in_block} }} sql(1) returned {account:?}, time: {} sec", + execution_time.as_secs_f64() + ); + + Ok(account) + } + async fn get_older_account_row_at( &self, pubkey: &str, slot: u64, ) -> ChResult> { - let query = r#" + let query = r" SELECT owner, lamports, executable, rent_epoch, data, txn_signature FROM events.older_account_distributed FINAL WHERE pubkey = ? AND slot <= ? ORDER BY slot DESC LIMIT 1 - "#; + "; Self::row_opt( self.client .query(query) @@ -367,26 +593,26 @@ impl ClickHouseDb { } async fn get_sol_sig_rooted_slot(&self, sol_sig: &[u8; 64]) -> ChResult> { - let query = r#" - SELECT slot, parent, status - FROM events.update_slot + let query = r" + SELECT slot, parent + FROM events.rooted_slots WHERE slot IN ( SELECT slot FROM events.notify_transaction_distributed WHERE signature = ? ) - AND status = 'Rooted' ORDER BY slot DESC LIMIT 1 - "#; + "; Self::row_opt( self.client .query(query) .bind(sol_sig.as_slice()) - .fetch_one::() + .fetch_one::() .await, ) + .map(|slot_parent_rooted_opt| slot_parent_rooted_opt.map(std::convert::Into::into)) .map_err(|e| { println!("get_sol_sig_rooted_slot error: {e}"); ChError::Db(e) @@ -395,7 +621,7 @@ impl ClickHouseDb { async fn get_sol_sig_confirmed_slot(&self, sol_sig: &[u8; 64]) -> ChResult> { let (_, slot_vec) = self.get_branch_slots(None).await?; - let query = r#" + let query = r" SELECT slot, parent, status FROM events.update_slot WHERE slot IN ? @@ -406,7 +632,7 @@ impl ClickHouseDb { ) ORDER BY slot DESC LIMIT 1 - "#; + "; Self::row_opt( self.client @@ -427,7 +653,7 @@ impl ClickHouseDb { &self, pubkey: &Pubkey, sol_sig: &[u8; 64], - ) -> ChResult> { + ) -> DbResult> { let sol_sig_str = bs58::encode(sol_sig).into_string(); info!("get_account_by_sol_sig {{ pubkey: {pubkey}, sol_sig: {sol_sig_str} }}"); let time_start = Instant::now(); @@ -448,20 +674,18 @@ impl ClickHouseDb { ); } - let slot = if let Some(slot) = slot_opt { - slot - } else { + let Some(slot) = slot_opt else { return Ok(None); }; // Try to find account changes within the given slot. - let query = r#" + let query = r" SELECT DISTINCT ON (pubkey, txn_signature, write_version) - owner, lamports, executable, rent_epoch, data, txn_signature + pubkey, owner, lamports, executable, rent_epoch, data, txn_signature FROM events.update_account_distributed WHERE slot = ? AND pubkey = ? ORDER BY write_version DESC - "#; + "; let pubkey_str = format!("{:?}", pubkey.to_bytes()); let time_start = Instant::now(); @@ -503,14 +727,14 @@ impl ClickHouseDb { return row_found .map(|row| { row.try_into() - .map_err(|err| ChError::Db(clickhouse::error::Error::Custom(err))) + .map_err(|err| anyhow!(ChError::Db(clickhouse::error::Error::Custom(err)))) }) .transpose(); } // If not found, get closest account state in one of previous slots if let Some(parent) = slot.parent { - self.get_account_at(pubkey, parent).await + self.get_account_at(pubkey, parent, None).await } else { Ok(None) } diff --git a/evm_loader/lib/src/types/tracer_rocks_db.rs b/evm_loader/lib/src/types/tracer_rocks_db.rs new file mode 100644 index 000000000..d36fb5218 --- /dev/null +++ b/evm_loader/lib/src/types/tracer_rocks_db.rs @@ -0,0 +1,162 @@ +use crate::account_data::AccountData; +use crate::config::RocksDbConfig; +use async_trait::async_trait; +use jsonrpsee::core::client::ClientT; +use jsonrpsee::core::Serialize; +use jsonrpsee::rpc_params; +use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use serde_json::from_str; +use solana_sdk::signature::Signature; +use solana_sdk::{ + account::Account, + clock::{Slot, UnixTimestamp}, + pubkey::Pubkey, +}; +use std::env; +use std::str::FromStr; +use std::sync::Arc; +use tracing::info; + +#[derive(Clone, Serialize)] +pub struct AccountParams { + pub pubkey: Pubkey, + pub slot: u64, + pub tx_index_in_block: Option, +} + +use crate::types::tracer_ch_common::{EthSyncStatus, RevisionMap}; +use crate::types::{DbResult, TracerDbTrait}; +// use reconnecting_jsonrpsee_ws_client::{Client, CallRetryPolicy, rpc_params, ExponentialBackoff}; +#[derive(Clone, Debug)] +pub struct RocksDb { + #[allow(dead_code)] + url: String, + client: Arc, +} + +impl RocksDb { + #[must_use] + pub async fn new(config: &RocksDbConfig) -> Self { + let host = &config.rocksdb_host; + let port = &config.rocksdb_port; + let url = format!("ws://{host}:{port}"); + + // match Client::builder() + // .retry_policy( + // ExponentialBackoff::from_millis(100) + // .max_delay(Duration::from_secs(10)) + // .take(3),) + match WsClientBuilder::default().build(&url).await { + Ok(client) => { + let arc_c = Arc::new(client); + tracing::info!("Created rocksdb client at {url}"); + Self { url, client: arc_c } + } + Err(e) => panic!("Couldn't start rocksDb client at {url}: {e}"), + } + } +} + +#[async_trait] +impl TracerDbTrait for RocksDb { + async fn get_block_time(&self, slot: Slot) -> DbResult { + let response: String = self + .client + .request("get_block_time", rpc_params![slot]) + .await?; + info!( + "get_block_time for slot {:?} response: {:?}", + slot, response + ); + Ok(i64::from_str(response.as_str())?) + } + + async fn get_earliest_rooted_slot(&self) -> DbResult { + let response: String = self + .client + .request("get_earliest_rooted_slot", rpc_params![]) + .await?; + info!("get_earliest_rooted_slot response: {:?}", response); + Ok(u64::from_str(response.as_str())?) + } + + async fn get_latest_block(&self) -> DbResult { + let response: String = self + .client + .request("get_last_rooted_slot", rpc_params![]) + .await?; + info!("get_latest_block response: {:?}", response); + Ok(u64::from_str(response.as_str())?) + } + + async fn get_account_at( + &self, + pubkey: &Pubkey, + slot: u64, + tx_index_in_block: Option, + ) -> DbResult> { + info!("get_account_at {pubkey:?}, slot: {slot:?}, tx_index: {tx_index_in_block:?}"); + + let response: String = self + .client + .request( + "get_account", + rpc_params![pubkey.to_string(), slot, tx_index_in_block], + ) + .await?; + + let account = from_str::>(response.as_str())?; + account.as_ref().map_or_else(|| { + info!("Got None for Account by {pubkey:?}"); + }, |account| { + info!("Got Account by {pubkey:?} owner: {:?} lamports: {:?} executable: {:?} rent_epoch: {:?}", account.owner, account.lamports, account.executable, account.rent_epoch); + }); + + Ok(account) + } + + async fn get_transaction_index(&self, signature: Signature) -> DbResult { + let response: String = self + .client + .request("get_transaction_index", rpc_params![signature.to_string()]) + .await?; + info!("get_transaction_index response: {:?}", response); + Ok(u64::from_str(response.as_str())?) + } + + async fn get_neon_revisions(&self, _pubkey: &Pubkey) -> DbResult { + let revision = env::var("NEON_REVISION").expect("NEON_REVISION should be set"); + + info!("get_neon_revisions for {revision:?}"); + let ranges = vec![(1, 100_000, revision)]; + Ok(RevisionMap::new(ranges)) + } + + async fn get_neon_revision(&self, slot: Slot, pubkey: &Pubkey) -> DbResult { + info!("get_neon_revision for {slot:?}, pubkey: {pubkey:?}"); + let neon_revision = env!("NEON_REVISION"); + Ok(neon_revision.to_string()) + } + + async fn get_slot_by_blockhash(&self, blockhash: String) -> DbResult { + let response: String = self + .client + .request("get_slot_by_blockhash", rpc_params![blockhash]) + .await?; + info!("response: {:?}", response); + Ok(from_str(response.as_str())?) + } + + async fn get_sync_status(&self) -> DbResult { + Ok(EthSyncStatus::new(None)) + } + + async fn get_accounts_in_transaction( + &self, + _sol_sig: &[u8], + _slot: u64, + ) -> DbResult> { + // TODO implement + Ok(Vec::new()) + } +} diff --git a/evm_loader/program-macro/Cargo.toml b/evm_loader/program-macro/Cargo.toml index 02d109b73..07bfee2be 100644 --- a/evm_loader/program-macro/Cargo.toml +++ b/evm_loader/program-macro/Cargo.toml @@ -9,9 +9,10 @@ authors = ["NeonLabs Maintainers "] proc-macro = true [dependencies] -syn = "1" +syn = {version = "2", features = ["full"] } +proc-macro2 = "1" quote = "1" -itertools = "0.10.5" -bs58 = "0.4" -serde = { version = "1", features = [ "derive" ] } -toml = "0.5" +itertools = "0.13" +bs58 = "0.5" +serde = { version = "1.0", features = [ "derive" ] } +toml = "0.8" diff --git a/evm_loader/program-macro/src/config_parser.rs b/evm_loader/program-macro/src/config_parser.rs index 7d79de7a2..62a05f15e 100644 --- a/evm_loader/program-macro/src/config_parser.rs +++ b/evm_loader/program-macro/src/config_parser.rs @@ -1,50 +1,102 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::path::PathBuf; use itertools::Itertools; -use proc_macro::TokenStream; +use proc_macro2::Literal; use quote::quote; use serde::Deserialize; use syn::{ parse::{Parse, ParseStream}, - parse_str, Expr, Ident, LitFloat, LitInt, LitStr, Type, + parse_str, Ident, Lit, LitBool, LitFloat, LitInt, LitStr, Type, }; +use toml::Table; #[derive(Deserialize)] pub struct NetSpecificConfig { - pub chain_id: u64, + pub program_id: String, pub operators_whitelist: Vec, - pub token_mint: TokenMint, + pub neon_chain_id: u64, + pub neon_token_mint: String, + pub chains: Vec, + pub no_update_tracking_owners: Vec, } impl Parse for NetSpecificConfig { fn parse(input: ParseStream) -> syn::Result { - let file_relative_path: LitStr = input.parse()?; - let mut file_path = PathBuf::new(); - file_path.push(std::env::var("CARGO_MANIFEST_DIR").map_err(|_| { - syn::Error::new( - input.span(), - "This proc macro should be called from a Cargo project", - ) - })?); - file_path.push(file_relative_path.value()); - let file_contents = std::fs::read(&file_path).map_err(|_| { - syn::Error::new( - input.span(), - format!("{} should be a valid path", file_path.display()), - ) - })?; - toml::from_slice(&file_contents).map_err(|e| syn::Error::new(input.span(), e.to_string())) + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let file_relative_path = input.parse::()?.value(); + + let file_path = PathBuf::from_iter([manifest_dir, file_relative_path]); + + let file_contents = std::fs::read_to_string(file_path).unwrap(); + + let root = file_contents.parse::().unwrap(); + + let program_id = root["program_id"].as_str().unwrap().to_string(); + let operators_whitelist = root["operators_whitelist"] + .as_array() + .unwrap() + .iter() + .map(|v| v.as_str().unwrap().to_string()) + .collect::>(); + + let no_update_tracking_owners = root["no_update_tracking_owners"] + .as_array() + .unwrap() + .iter() + .map(|v| v.as_str().unwrap().to_string()) + .collect::>(); + + let chains = root["chain"] + .as_table() + .unwrap() + .iter() + .map(|(name, table)| { + let table = table.as_table().unwrap(); + Chain { + id: table["id"].as_integer().unwrap().try_into().unwrap(), + name: name.clone(), + token: table["token"].as_str().unwrap().to_string(), + } + }) + .collect::>(); + + let (neon_chain_id, neon_token_mint) = chains + .iter() + .find_map(|c| { + if c.name == "neon" { + Some((c.id, c.token.clone())) + } else { + None + } + }) + .unwrap(); + + Ok(Self { + program_id, + operators_whitelist, + neon_chain_id, + neon_token_mint, + chains, + no_update_tracking_owners, + }) } } -#[derive(Deserialize)] -pub struct TokenMint { - pub neon_token_mint: String, - pub decimals: u8, +#[derive(Deserialize, Debug)] +pub struct Chain { + pub id: u64, + pub name: String, + pub token: String, +} + +pub struct CommonVariable { + pub name: Ident, + pub r#type: Type, + pub value: Lit, } pub struct CommonConfig { - pub token_stream: TokenStream, + pub variables: Vec, } impl Parse for CommonConfig { @@ -58,58 +110,62 @@ impl Parse for CommonConfig { ) })?); file_path.push(file_relative_path.value()); - let file_contents = std::fs::read(&file_path).map_err(|_| { + let file_contents = std::fs::read_to_string(&file_path).map_err(|_| { syn::Error::new( input.span(), format!("{} should be a valid path", file_path.display()), ) })?; - let config: HashMap = toml::from_slice(&file_contents) + let config = file_contents + .parse::
() .map_err(|e| syn::Error::new(input.span(), e.to_string()))?; + let variables: Vec<_> = config .into_iter() .map(move |(name, value)| { - let uppercased_name = name.to_uppercase(); - let ident_name: Ident = parse_str(&uppercased_name)?; - let neon_ident_name: Ident = parse_str(&format!("NEON_{uppercased_name}"))?; + let name = name.to_uppercase(); + let name: Ident = parse_str(&name)?; + match value { - toml::Value::Float(v) => { - let v: LitFloat = parse_str(&v.to_string())?; - Ok(quote! { - pub const #ident_name: f64 = #v; - neon_elf_param!(#neon_ident_name, formatcp!("{}", #ident_name)); - }) - } - toml::Value::Integer(v) => { - let v: LitInt = parse_str(&v.to_string())?; - Ok(quote! { - pub const #ident_name: u64 = #v; - neon_elf_param!(#neon_ident_name, formatcp!("{}", #ident_name)); - }) - } - toml::Value::String(v) => Ok(quote! { - pub const #ident_name: &str = #v; - neon_elf_param!(#neon_ident_name, formatcp!("{}", #ident_name)); + toml::Value::Float(v) => Ok(CommonVariable { + name, + r#type: Type::Verbatim(quote!(f64)), + value: Lit::new(Literal::f64_unsuffixed(v)), + }), + toml::Value::Integer(v) => Ok(CommonVariable { + name, + r#type: Type::Verbatim(quote!(u64)), + value: Lit::new(Literal::i64_unsuffixed(v)), }), - toml::Value::Boolean(v) => Ok(quote! { - pub const #ident_name: bool = #v; - neon_elf_param!(#neon_ident_name, formatcp!("{}", #ident_name)); + toml::Value::String(v) => Ok(CommonVariable { + name, + r#type: Type::Verbatim(quote!(&str)), + value: Lit::Str(LitStr::new(&v, input.span())), }), - toml::Value::Array(ref array) => match (array.get(0), array.get(1)) { + toml::Value::Boolean(v) => Ok(CommonVariable { + name, + r#type: Type::Verbatim(quote!(bool)), + value: Lit::Bool(LitBool::new(v, input.span())), + }), + toml::Value::Array(ref array) => match (array.first(), array.get(1)) { (Some(toml::Value::Integer(v)), Some(toml::Value::String(t))) => { - let v: LitInt = parse_str(&v.to_string())?; + let s = v.to_string(); + let v: LitInt = parse_str(&s)?; let t: Type = parse_str(t)?; - Ok(quote! { - pub const #ident_name: #t = #v; - neon_elf_param!(#neon_ident_name, formatcp!("{}", #ident_name)); + Ok(CommonVariable { + name, + r#type: t, + value: Lit::Int(v), }) } (Some(toml::Value::Float(v)), Some(toml::Value::String(t))) => { - let v: LitFloat = parse_str(&v.to_string())?; + let s = v.to_string(); + let v: LitFloat = parse_str(&s)?; let t: Type = parse_str(t)?; - Ok(quote! { - pub const #ident_name: #t = #v; - neon_elf_param!(#neon_ident_name, formatcp!("{}", #ident_name)); + Ok(CommonVariable { + name, + r#type: t, + value: Lit::Float(v), }) } _ => Err(syn::Error::new( @@ -123,69 +179,8 @@ impl Parse for CommonConfig { )), } }) - .flatten_ok() .try_collect()?; - Ok(Self { - token_stream: quote! {#(#variables)*}.into(), - }) - } -} - -#[derive(Deserialize)] -struct InternalElfParams { - env: HashMap, - formatcp: HashMap, -} - -pub struct ElfParams { - pub token_stream: TokenStream, -} - -impl Parse for ElfParams { - fn parse(input: ParseStream) -> syn::Result { - let file_relative_path: LitStr = input.parse()?; - let mut file_path = PathBuf::new(); - file_path.push(std::env::var("CARGO_MANIFEST_DIR").map_err(|_| { - syn::Error::new( - input.span(), - "This proc macro should be called from a Cargo project", - ) - })?); - file_path.push(file_relative_path.value()); - let file_contents = std::fs::read(&file_path).map_err(|_| { - syn::Error::new( - input.span(), - format!("{} should be a valid path", file_path.display()), - ) - })?; - let InternalElfParams { env, formatcp } = toml::from_slice(&file_contents) - .map_err(|e| syn::Error::new(input.span(), e.to_string()))?; - let env_tokens = env - .into_iter() - .map(|(name, env_name)| { - let name_ident: Ident = parse_str(&name.to_uppercase())?; - Ok(quote! { neon_elf_param!(#name_ident, env!(#env_name)); }) - }) - .flatten_ok() - .try_collect::<_, Vec<_>, syn::Error>()?; - - let formatcp_tokens = formatcp - .into_iter() - .map(|(name, value)| { - let name_ident: Ident = parse_str(&name.to_uppercase())?; - let value_expr: Expr = parse_str(&value)?; - Ok(quote! { neon_elf_param!(#name_ident, formatcp!("{}", #value_expr)); }) - }) - .flatten_ok() - .try_collect::<_, Vec<_>, syn::Error>()?; - - Ok(Self { - token_stream: quote! { - #(#env_tokens)* - #(#formatcp_tokens)* - } - .into(), - }) + Ok(Self { variables }) } } diff --git a/evm_loader/program-macro/src/lib.rs b/evm_loader/program-macro/src/lib.rs index ed88c6dff..2b3f2000d 100644 --- a/evm_loader/program-macro/src/lib.rs +++ b/evm_loader/program-macro/src/lib.rs @@ -3,54 +3,20 @@ mod config_parser; -use config_parser::{CommonConfig, ElfParams, NetSpecificConfig, TokenMint}; +use std::collections::BTreeMap; + +use config_parser::{CommonConfig, NetSpecificConfig}; use proc_macro::TokenStream; use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::{parse_macro_input, Expr, Ident, LitStr, Result, Token}; +use syn::{ + parse_macro_input, Data::Struct, DataStruct, DeriveInput, Expr, Fields::Named, FieldsNamed, + GenericArgument, Ident, LitStr, PathArguments, Result, Token, Type, TypePath, TypeTuple, +}; use quote::quote; extern crate proc_macro; -struct OperatorsWhitelistInput { - list: Punctuated, -} - -impl Parse for OperatorsWhitelistInput { - fn parse(input: ParseStream) -> Result { - let list = Punctuated::parse_terminated(input)?; - Ok(Self { list }) - } -} - -#[proc_macro] -pub fn operators_whitelist(tokens: TokenStream) -> TokenStream { - let input = parse_macro_input!(tokens as OperatorsWhitelistInput); - - let mut operators: Vec> = input - .list - .iter() - .map(LitStr::value) - .map(|key| { - bs58::decode(key) - .into_vec() - .expect("Pubkey is base64 encoded") - }) - .collect(); - - operators.sort_unstable(); - - let len = operators.len(); - - quote! { - pub static AUTHORIZED_OPERATOR_LIST: [::solana_program::pubkey::Pubkey; #len] = [ - #(::solana_program::pubkey::Pubkey::new_from_array([#((#operators),)*]),)* - ]; - } - .into() -} - struct ElfParamInput { name: Ident, _separator: Token![,], @@ -94,88 +60,199 @@ pub fn neon_elf_param(tokens: TokenStream) -> TokenStream { .into() } -struct ElfParamIdInput { - name: Ident, - _separator: Token![,], - value: LitStr, -} +/// # Panics +/// Panic at compile time if config file is not correct +#[proc_macro] +pub fn net_specific_config_parser(tokens: TokenStream) -> TokenStream { + let NetSpecificConfig { + program_id, + neon_chain_id, + neon_token_mint, + operators_whitelist, + no_update_tracking_owners, + mut chains, + } = parse_macro_input!(tokens as NetSpecificConfig); -impl Parse for ElfParamIdInput { - fn parse(input: ParseStream) -> Result { - Ok(Self { - name: input.parse()?, - _separator: input.parse()?, - value: input.parse()?, - }) - } -} + let mut operators: Vec> = operators_whitelist + .iter() + .map(|key| bs58::decode(key).into_vec().unwrap()) + .collect(); -#[proc_macro] -pub fn declare_param_id(tokens: TokenStream) -> TokenStream { - let input = parse_macro_input!(tokens as ElfParamIdInput); + operators.sort_unstable(); + let operators_len = operators.len(); - let name = input.name; + let mut no_update_tracking_owners: Vec> = no_update_tracking_owners + .iter() + .map(|key| bs58::decode(key).into_vec().unwrap()) + .collect(); + + no_update_tracking_owners.sort_unstable(); + let no_update_tracking_owners_len = no_update_tracking_owners.len(); - let value = input.value.value(); - let value_bytes = value.as_bytes(); + chains.sort_unstable_by_key(|c| c.id); + let chains_len = chains.len(); - let len = value.len(); + let chain_ids = chains.iter().map(|c| c.id).collect::>(); + let chain_names = chains.iter().map(|c| c.name.clone()).collect::>(); + let chain_tokens = chains + .iter() + .map(|c| bs58::decode(&c.token).into_vec().unwrap()) + .collect::>(); + + let neon_chain_id_str = neon_chain_id.to_string(); quote! { - ::solana_program::declare_id!(#value); + pub const PROGRAM_ID: solana_program::pubkey::Pubkey = solana_program::pubkey!(#program_id); + pub const DEFAULT_CHAIN_ID: u64 = #neon_chain_id; - #[no_mangle] - #[used] - #[doc(hidden)] - pub static #name: [u8; #len] = [ - #((#value_bytes),)* + neon_elf_param!(NEON_CHAIN_ID, #neon_chain_id_str); + neon_elf_param!(NEON_TOKEN_MINT, #neon_token_mint); + + pub const AUTHORIZED_OPERATOR_LIST: [::solana_program::pubkey::Pubkey; #operators_len] = [ + #(::solana_program::pubkey::Pubkey::new_from_array([#((#operators),)*]),)* + ]; + + pub const NO_UPDATE_TRACKING_OWNERS: [::solana_program::pubkey::Pubkey; #no_update_tracking_owners_len] = [ + #(::solana_program::pubkey::Pubkey::new_from_array([#((#no_update_tracking_owners),)*]),)* + ]; + + pub const CHAIN_ID_LIST: [(u64, &str, ::solana_program::pubkey::Pubkey); #chains_len] = [ + #( (#chain_ids, #chain_names, ::solana_program::pubkey::Pubkey::new_from_array([#(#chain_tokens),*])) ),* ]; } .into() } #[proc_macro] -pub fn net_specific_config_parser(tokens: TokenStream) -> TokenStream { - let NetSpecificConfig { - chain_id, - operators_whitelist, - token_mint: TokenMint { - neon_token_mint, - decimals, - }, - } = parse_macro_input!(tokens as NetSpecificConfig); +pub fn common_config_parser(tokens: TokenStream) -> TokenStream { + let config = parse_macro_input!(tokens as CommonConfig); - quote! { - /// Supported CHAIN_ID value for transactions - pub const CHAIN_ID: u64 = #chain_id; + let mut variables = BTreeMap::new(); + let mut tokens = Vec::::new(); + + for v in config.variables { + let t = v.r#type; + let name = v.name; + let value = v.value; + + let elf_name_string = "NEON_".to_string() + &name.to_string(); + let elf_name = Ident::new(&elf_name_string, name.span()); + let elf_value = match &value { + syn::Lit::Str(s) => s.clone(), + syn::Lit::Int(i) => LitStr::new(&i.to_string(), i.span()), + syn::Lit::Float(f) => LitStr::new(&f.to_string(), f.span()), + syn::Lit::Bool(b) => LitStr::new(&b.value().to_string(), b.span()), + _ => unreachable!(), + }; - operators_whitelist![#(#operators_whitelist),*]; + tokens.push(quote! { + pub const #name: #t = #value; + neon_elf_param!(#elf_name, #elf_value); + }); - /// Token Mint ID - pub mod token_mint { - use super::declare_param_id; + variables.insert(elf_name_string, elf_value); + } - declare_param_id!(NEON_TOKEN_MINT, #neon_token_mint); - /// Ethereum account version - pub const DECIMALS: u8 = #decimals; + let variables_len = variables.len(); + let variable_names = variables.keys(); + let variable_values = variables.values(); - /// Number of base 10 digits to the right of the decimal place - #[must_use] - pub const fn decimals() -> u8 { DECIMALS } + quote! { + #(#tokens)* + pub const PARAMETERS: [(&str, &str); #variables_len] = [ + #( (#variable_names, #variable_values) ),* + ]; + } + .into() +} + +#[proc_macro_derive(ReconstructRaw)] +pub fn reconstruct_raw(input: TokenStream) -> TokenStream { + // Parse the string representation + let ast = parse_macro_input!(input as DeriveInput); + + let Struct(DataStruct { + fields: Named(FieldsNamed { ref named, .. }), + .. + }) = ast.data + else { + unimplemented!("ReconstructRaw only works for structs"); + }; + let builder_fields = named.iter().map(|f| { + let name = &f.ident; + let ty = &f.ty; + + // If the type of the field is Vector, use a special function to reconstruct it. + // Only Vectors of primitive types are supported. + // Other Vectors (including Vector>) are constructed empty. + // N.B. Currently, it's only used in the Transaction in the context of the Core API. + // The only composite vector is access_list which is not relevant for the Core API. + if !is_vector_type(ty) { + quote! { #name: std::ptr::read_unaligned(std::ptr::addr_of!((*struct_ptr).#name)) } + } else if is_composite_vector_type(ty) { + quote! { #name: vector![] } + } else { + quote! { #name: read_vec(std::ptr::addr_of!((*struct_ptr).#name).cast::(), offset).into_vector() } + } + }); + + let name = &ast.ident; + quote! { + impl ReconstructRaw for #name { + /// # Safety + /// Generated code, if something goes wrong here, it likely won't compile at all. + unsafe fn build(struct_ptr: *const Self, offset: isize) -> Self { + unsafe { + Self { + #(#builder_fields,)* + } + } + } } } .into() } -#[proc_macro] -pub fn common_config_parser(tokens: TokenStream) -> TokenStream { - let config = parse_macro_input!(tokens as CommonConfig); - config.token_stream +fn is_vector_type(ty: &Type) -> bool { + match ty { + Type::Path(TypePath { + path: path_type, .. + }) => path_type + .segments + .iter() + .any(|f| f.ident.to_string().eq("Vector")), + Type::Tuple(TypeTuple { + elems: elems_type, .. + }) => elems_type.iter().any(is_vector_type), + _ => false, + } } -#[proc_macro] -pub fn elf_config_parser(tokens: TokenStream) -> TokenStream { - let config = parse_macro_input!(tokens as ElfParams); - config.token_stream +fn is_argument_vector_type(arg: &PathArguments) -> bool { + match arg { + PathArguments::AngleBracketed(inner_arg) => inner_arg.args.iter().any(|f| match f { + GenericArgument::Type(inner_type) => is_vector_type(inner_type), + _ => false, + }), + _ => false, + } +} + +fn is_composite_vector_type(ty: &Type) -> bool { + if let Type::Path(TypePath { + qself: _, + path: path_type, + }) = ty + { + let vec_path_segment = path_type + .segments + .iter() + .find(|&f| f.ident.to_string().eq("Vector")); + if let Some(path_segment) = vec_path_segment { + return is_argument_vector_type(&path_segment.arguments); + } + return false; + } + false } diff --git a/evm_loader/program/Cargo.toml b/evm_loader/program/Cargo.toml index cfc046c5f..00763c75a 100644 --- a/evm_loader/program/Cargo.toml +++ b/evm_loader/program/Cargo.toml @@ -1,9 +1,8 @@ - -# Note: This crate must be built using cargo build-bpf +# Note: This crate must be built using cargo build-sbf [package] name = "evm-loader" -version = "1.1.0-dev" +version = "1.15.0-dev" description = "Neon EVM loader" authors = ["NeonLabs Maintainers "] edition = "2021" @@ -21,6 +20,8 @@ testnet = [] devnet = [] ## Builds NeonEVM for CI environment ci = [] +## Builds NeonEVM for rollup deployment with settings adjusted. +rollup = [] ## Builds NeonEVM program for `emergency` mode. In this mode, NeonEVM doesn't process ## any transaction and return error `ProgramError::InvalidInstructionData` with comment @@ -34,33 +35,40 @@ no-entrypoint = [] test-bpf = [] custom-heap = [] default = ["custom-heap"] -tracing = ["environmental"] [dependencies] -linked_list_allocator = { version = "0.10", default_features = false } +linked_list_allocator = { version = "0.10", default-features = false } evm-loader-macro = { path = "../program-macro" } -solana-program = { version = "=1.14.20", default_features = false } -spl-token = { version = "~3.5", default_features = false, features = ["no-entrypoint"] } -spl-associated-token-account = { version = "~1.1", default_features = false, features = ["no-entrypoint"] } -mpl-token-metadata = { version = "1.12", default_features = false, features = ["no-entrypoint"] } +solana-program.workspace = true +spl-token = { version = "~4.0", default-features = false, features = ["no-entrypoint"] } +spl-associated-token-account = { version = "~2.3", default-features = false, features = ["no-entrypoint"] } +mpl-token-metadata = { version = "~4.1", default-features = false } thiserror = "1.0" -arrayref = "0.3.6" -hex = "0.4.2" +arrayref = "0.3.8" +hex = "0.4.3" ripemd = "0.1" rlp = "0.5" static_assertions = "1" -borsh = "0.9" +borsh = "0.10" bincode = "1" -serde_bytes = "0.11" -serde = { version = "1", features = ["derive"] } -ethnum = { version = "1", default_features = false, features = [ "serde" ] } -const_format = { version = "0.2.21" } +serde_bytes = "0.11.15" +serde = { version = "1.0.204", default-features = false, features = ["derive", "rc"] } +ethnum = { version = "1.5", default-features = false, features = ["serde"] } cfg-if = { version = "1.0" } log = { version = "0.4", default-features = false, optional = true } -environmental = { version = "1", default-features = false, optional = true} +maybe-async = "0.2.10" +async-trait = { version = "0.1.81", optional = true } +allocator-api2 = "0.2.16" +memoffset = "0.9.1" + +[target.'cfg(target_os = "solana")'.dependencies.maybe-async] +version = "0.2.10" +features = ["is_sync"] [dev-dependencies] -serde_json = "1" +tokio = { version = "1.39", features = ["full"] } +solana-sdk.workspace = true + [lib] crate-type = ["cdylib", "lib"] diff --git a/evm_loader/program/config/common.toml b/evm_loader/program/config/common.toml index 7fe56f187..3ff81fe8d 100644 --- a/evm_loader/program/config/common.toml +++ b/evm_loader/program/config/common.toml @@ -1,14 +1,9 @@ account_seed_version = [3, "u8"] payment_to_treasure = 5000 -payment_to_deposit = 5000 -operator_priority_slots = 16 holder_msg_size = 950 -request_units_additional_fee = 0 evm_steps_min = 500 -evm_steps_last_iteration_max = 1 -compute_budget_units = 500_000 -compute_budget_heap_frame = 262144 # 256 * 1024 +evm_steps_last_iteration_max = 0 gas_limit_multiplier_no_chainid = 1000 -storage_entries_in_contract_account = [64, "u32"] +storage_entries_in_contract_account = [64, "usize"] treasury_pool_count = 128 treasury_pool_seed = "treasury_pool" diff --git a/evm_loader/program/config/default.toml b/evm_loader/program/config/default.toml index 97eca7604..5939e4b37 100644 --- a/evm_loader/program/config/default.toml +++ b/evm_loader/program/config/default.toml @@ -1,4 +1,7 @@ -chain_id = 111 +program_id = "53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io" + +no_update_tracking_owners = [] + operators_whitelist = [ "9kPRbbwKL5SYELF4cZqWWFmP88QkKys51DoaUBx8eK73", "BMp6gEnveANdvSvspESJUrNczuHz1GF5UQKjVLCkAZih", @@ -33,6 +36,18 @@ operators_whitelist = [ "eXiURdoUQ4JpUysAevcTPiLMdWwG8q6mRAmice5Kioh", ] -[token_mint] -neon_token_mint = "HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU" -decimals = 9 +[chain.neon] +id = 111 +token = "HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU" + +[chain.sol] +id = 112 +token = "So11111111111111111111111111111111111111112" + +[chain.usdt] +id = 113 +token = "2duuuuhNJHUYqcnZ7LKfeufeeTBgSJdftf2zM3cZV6ym" + +[chain.eth] +id = 114 +token = "EwJYd3UAFAgzodVeHprB2gMQ68r4ZEbbvpoVzCZ1dGq5" \ No newline at end of file diff --git a/evm_loader/program/config/devnet.toml b/evm_loader/program/config/devnet.toml index c2cb2bda1..656d3add3 100644 --- a/evm_loader/program/config/devnet.toml +++ b/evm_loader/program/config/devnet.toml @@ -1,4 +1,11 @@ -chain_id = 245022926 +program_id = "eeLSJgWzzxrqKv1UxtRVVH8FX3qCQWUs9QuAjJpETGU" + +no_update_tracking_owners = [ + "HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny", # Chainlink oracle + "rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ", # Pyth oracle + "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s", # Pyth legacy oracle +] + operators_whitelist = [ "NeoQM3utcHGxhKT41Nq81g8t4xGcPNFpkAgYj1N2N8v", "Gw3Xiwve6HdvpJeQguhwT23cpK9nRjSy1NpNYCFY4XU9", @@ -390,6 +397,22 @@ operators_whitelist = [ "E4sFXJ4p8CcxA2A5GsdjWRaSgPLvGNDdmFPyg65eoXrh", ] -[token_mint] -neon_token_mint = "89dre8rZjLNft7HoupGiyxu3MNftR577ZYu8bHe2kK7g" -decimals = 9 +[chain.neon] +id = 245022926 +token = "89dre8rZjLNft7HoupGiyxu3MNftR577ZYu8bHe2kK7g" + +[chain.sol] +id = 245022927 +token = "So11111111111111111111111111111111111111112" + +[chain.usdc] +id = 245022928 +token = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" + +[chain.usdt] +id = 245022929 +token = "2duuuuhNJHUYqcnZ7LKfeufeeTBgSJdftf2zM3cZV6ym" + +[chain.eth] +id = 245022930 +token = "EwJYd3UAFAgzodVeHprB2gMQ68r4ZEbbvpoVzCZ1dGq5" \ No newline at end of file diff --git a/evm_loader/program/config/elf_params.toml b/evm_loader/program/config/elf_params.toml deleted file mode 100644 index 5657613d5..000000000 --- a/evm_loader/program/config/elf_params.toml +++ /dev/null @@ -1,13 +0,0 @@ -[env] -neon_pkg_version = "CARGO_PKG_VERSION" -neon_revision = "NEON_REVISION" - -[formatcp] -neon_seed_version = "ACCOUNT_SEED_VERSION" -neon_token_mint_decimals = "token_mint::DECIMALS" -neon_chain_id = "CHAIN_ID" -neon_compute_units = "COMPUTE_BUDGET_UNITS" -neon_heap_frame = "COMPUTE_BUDGET_HEAP_FRAME" -neon_additional_fee = "REQUEST_UNITS_ADDITIONAL_FEE" -neon_pool_count = "TREASURY_POOL_COUNT" -neon_pool_seed = "TREASURY_POOL_SEED" diff --git a/evm_loader/program/config/govertest.toml b/evm_loader/program/config/govertest.toml index 843205930..cce68f0b8 100644 --- a/evm_loader/program/config/govertest.toml +++ b/evm_loader/program/config/govertest.toml @@ -1,4 +1,7 @@ -chain_id = 111 +program_id = "53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io" + +no_update_tracking_owners = [] + operators_whitelist = [ "9kPRbbwKL5SYELF4cZqWWFmP88QkKys51DoaUBx8eK73", "BMp6gEnveANdvSvspESJUrNczuHz1GF5UQKjVLCkAZih", @@ -33,6 +36,6 @@ operators_whitelist = [ "eXiURdoUQ4JpUysAevcTPiLMdWwG8q6mRAmice5Kioh", ] -[token_mint] -neon_token_mint = "EjLGfD8mpxKLwGDi8AiTisAbGtWWM2L3htkJ6MpvS8Hk" -decimals = 9 +[chain.neon] +id = 111 +token = "EjLGfD8mpxKLwGDi8AiTisAbGtWWM2L3htkJ6MpvS8Hk" diff --git a/evm_loader/program/config/mainnet.toml b/evm_loader/program/config/mainnet.toml index 0f1e87382..b037c0165 100644 --- a/evm_loader/program/config/mainnet.toml +++ b/evm_loader/program/config/mainnet.toml @@ -1,4 +1,11 @@ -chain_id = 245022934 +program_id = "NeonVMyRX5GbCrsAHnUwx1nYYoJAtskU1bWUo6JGNyG" + +no_update_tracking_owners = [ + "HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny", # Chainlink oracle + "rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ", # Pyth oracle + "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH", # Pyth legacy oracle +] + operators_whitelist = [ "NeonPQFrw5stVvs1rFLDxALWUBDCnSPsWBP83RfNUKK", "GYt9w8MaXztDLhhsxmQr7Ar9FJ6MmaFwav7qBrxZKwhd", @@ -393,6 +400,6 @@ operators_whitelist = [ "DYS3mepDkhT62A2aJvnPmreUS74Uec34cnPn85AaZq3k", ] -[token_mint] -neon_token_mint = "NeonTjSjsuo3rexg9o6vHuMXw62f9V7zvmu8M8Zut44" -decimals = 9 +[chain.neon] +id = 245022934 +token = "NeonTjSjsuo3rexg9o6vHuMXw62f9V7zvmu8M8Zut44" diff --git a/evm_loader/program/config/rollup.toml b/evm_loader/program/config/rollup.toml new file mode 100644 index 000000000..b19446e01 --- /dev/null +++ b/evm_loader/program/config/rollup.toml @@ -0,0 +1,60 @@ +program_id = "EgbRZxFRQTiZpQGinE5jT6KQq5jtVnjtLdSTkW5UTcAv" + +no_update_tracking_owners = [] + +operators_whitelist = [ + "2Gsipy7yniskehFAccTp4ShhmdTiP8UsLWVFdxsG6ZGt", + "2fx77qnumM3sw5duzMYod8c8xSLavE4RwJAdNdeYZpFq", + "2wMN3y81BXQmcyihHrByDChop6qnF1UX5f4vAp8DYjVd", + "32U3DCL5gDNqqgfHsXuVnyJSnN6WympgBuR8TThXD2FR", + "3HXb2SsmQSaZ3MyB6g2b1BCtL8e7DcUwBjDRAytbiVrP", + "3tsPHg5cKnJok5N9W911QFmyb8iqSaKP4fKFqXjBciCF", + "4Pe48zv2qQRswyioywD6Ym21YvF9u57zo5cbLJDpSJnC", + "4hsUEQrpJiFo2gfNEaZoPKvVcgWDeWWCtUNWYCHfgiMd", + "5283XRV4NsMyyLoGcQWMMEY89eB8FgRw7VgzSKqqepCf", + "63tpScHzNbjQAi1AgkF6jJ1qtGjVq7aB8yLT4CJm8LvE", + "6C5nNpaV6mot83Lh7rtMpYUVFfUAVHH6nmpC96mRh8dp", + "7WCfs1Xe6F7M3ZwGu9BjVFhBcThrBZv2wtQ5CgvnWS54", + "7ikHZa3j4xD6s9DKpTpAxAoe4uG9NMkty5CCmU4p3iWp", + "7sU2USEHu7wkJFLJXza3bgCNwzFN3nddDhUeVoFiXV4Y", + "8BRoxMVsQYjpX137ptnH1kqx6vHLk2C9QQZ7hWhPwzAC", + "8hdWEvE5zqv3LAZ5io1M4mFW55w9frmowEEnnmbaFTsK", + "8xzLMMVwChuFo4XZAZqsz5HtVqHTcSu7CJqogebaw3nU", + "9DdU1ufgfe5Z7EZFuJragvkfysn4FBXto9LFMmiWCsF", + "9EaJPCqHcJvDtJTkAQWqeLj97FQpiNZiqq49hTxM3B35", + "9JY641aUoEMPzEUng6NpSfejg8A4hKk7YQxUo6f3R744", + "9Ro3VV9kaBj7p21XzQB3LJQfznZkkr3t7qziWKZVbKuU", + "ABfwv3nHL6b5ogrc3LBbUDnfYrc1JGUX2DDYG4njxGhH", + "AD3trvWBkUYoE4kBLyTP7o2Ej8aiyCFgD8Ctoi4Tejoz", + "ANprCKGACpkSByqyQQw2J4YEq1U68ouzx49vinAug5kH", + "As2d7rKDnw2R366tvz7FuL57T2X1y8mVyHEuVwXBA6iQ", + "BPPwRf4Stpecjr1YyxsBDSsEnHhPM87dcbmJLMxCfNKV", + "BcVupnAtrZXQ5SCyGa8g7BWwT9uD3TMv5e5sEqejay1", + "Bkf59SXA5ziH4jmjKU5nZB3SZoXAVXBT2znkiotR7bcR", + "BsXm9NVHDyqmzVB9eUHmDXWJX7M5qxcxfxqQiXxWgX9u", + "CjepUsoZ39xjtsMaF2mjV27AKAMMQ2AXbY6naWxLfEb6", + "CsWCnfXuibRmz6QM6HNqh5agLshMZV1utMrBPCRhcpZk", + "CzdHFcdazNAbJoKJeJWH9fJPNP2sbdSztQgrxs94u6Ps", + "D9iXY6KVBJtddGCd3fghMw8ik5jEet1rVR2bmTeKDJrC", + "DQzhkXK6DLYTbAXMFdZf3PeNX8o6nLJpHb526S7KjqXL", + "DhorUW7jUiFTeDTSZvp5F3e22nEBQWg6Pf411366SmtG", + "DpdcebJnjPHwMkzDconJi95o7MDQ2kY1e1Lqwk4k9fyE", + "EW5b5UG5Wp6dL5qvvqV9aJTYN7xYis429f7yU16gezPG", + "EaNNJzeyGpcQXAv43JHFkLQD3sKxHL7s3JAbsyJEvtwp", + "EoqWxp9iCzo1VGs6C6awGLrmz5hQCRKeumg4NpciQCiA", + "F2KvM8yD2epCjmh6dgzfUs1RNZ1nZV4Jbcufupq1Ex5r", + "F4NJurNwkT7pcdvsNgCwHyJWghwBEcRiab2796QFsMG6", + "FCzSWq6W4DZzqsXHVD85Ugt3WRtqspT2ZVxEphuD3Ege", + "FNvRosMS9PtEUiyGZNg6gjbw7u77wc14C8P44aRj9vKr", + "FSW7SmbZ8aqbJXgeg3SMC93ZvauGnmVmyGxgoucXqP7a", + "GdcR94SPum5uT4iDqp19GmjNtLZaxHWb4Ex1rDFK6Jjo", + "H9Nx2pc4eXNTXjiz8x1Sah9WD1SHzeF7u2yUdZuwmDTm", + "HKpXxXf2LDPdcuiEjDVnRMZHXXXCcbW8RmQHByGzHVgE", + "HX5Lv4XzTr4BVbmDM6t5UTxEKpjS4icrEFTufrdAygZC", + "Hd4Yt22xxanJbf4bDwdgyQrXFGEFrGKfQpTzK1isrbTS", + "HjSQ3GLiGF1mEXx92FQiXG67qQfaNQF7QZbstHYEouZe", +] + +[chain.neon] +id = 245022929 +token = "9ChfzoAfqGcrnK9c7cqQwd6MGrFqxA22Q9vLsS75hDzj" diff --git a/evm_loader/program/config/testnet.toml b/evm_loader/program/config/testnet.toml index 17811bdcf..ef01514c7 100644 --- a/evm_loader/program/config/testnet.toml +++ b/evm_loader/program/config/testnet.toml @@ -1,4 +1,7 @@ -chain_id = 245022940 +program_id = "eeLSJgWzzxrqKv1UxtRVVH8FX3qCQWUs9QuAjJpETGU" + +no_update_tracking_owners = [] + operators_whitelist = [ "NeoQM3utcHGxhKT41Nq81g8t4xGcPNFpkAgYj1N2N8v", "Gw3Xiwve6HdvpJeQguhwT23cpK9nRjSy1NpNYCFY4XU9", @@ -229,8 +232,11 @@ operators_whitelist = [ "AuikYUkrP9bRCxPq99YpEkFCgWLS9KM2oe3sCPkTCEwr", "AyEE2tf4AezMxtBYXoWgoK1PwMDMsPfDahQRtZvU8BLc", "B5Gefd2yR3nBi4eFDtp3grmVsRq6sw4UYmGVZG6vrda3", + "7P1VpfLJNo1rMJbHmz2P6U34ygkRM5UNogknFUXP2b1k", + "8HzCjhBNP3rs7SydUrZAiQGEoqXHNtpNPE475zzHmzba", + "CRJ7MFYvMjXysVDkifFmiS8jmpDMS5qZRwyu3EN3Rfav", ] -[token_mint] -neon_token_mint = "89dre8rZjLNft7HoupGiyxu3MNftR577ZYu8bHe2kK7g" -decimals = 9 +[chain.neon] +id = 245022940 +token = "89dre8rZjLNft7HoupGiyxu3MNftR577ZYu8bHe2kK7g" diff --git a/evm_loader/program/src/account/ether_account.rs b/evm_loader/program/src/account/ether_account.rs deleted file mode 100644 index af9dea29f..000000000 --- a/evm_loader/program/src/account/ether_account.rs +++ /dev/null @@ -1,175 +0,0 @@ -#![allow(clippy::use_self)] // Can't use generic parameter from outer function - -use std::mem::size_of; - -use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; -use ethnum::U256; -use solana_program::account_info::AccountInfo; -use solana_program::program_error::ProgramError; -use solana_program::{entrypoint::ProgramResult, pubkey::Pubkey}; - -use crate::types::Address; - -use super::{program::System, EthereumAccount, Packable}; -use super::{Operator, ACCOUNT_SEED_VERSION}; - -/// Ethereum account data v3 -#[derive(Debug, Default)] -pub struct Data { - /// Ethereum address - pub address: Address, - /// Solana account nonce - pub bump_seed: u8, - /// Ethereum account nonce - pub trx_count: u64, - /// Neon token balance - pub balance: U256, - /// Account generation, increment on suicide - pub generation: u32, - /// Contract code size - pub code_size: u32, - /// Read-write lock - pub rw_blocked: bool, -} - -impl Data { - const ADDRESS_SIZE: usize = size_of::
(); - const BUMP_SEED_SIZE: usize = size_of::(); - const TRX_COUNT_SIZE: usize = size_of::(); - const BALANCE_SIZE: usize = size_of::(); - const GENERATION_SIZE: usize = size_of::(); - const CODE_SIZE_SIZE: usize = size_of::(); - const RW_BLOCKED_SIZE: usize = size_of::(); -} - -impl Packable for Data { - /// `AccountV3` struct tag - const TAG: u8 = super::TAG_ACCOUNT_V3; - - /// `AccountV3` struct serialized size - const SIZE: usize = Data::ADDRESS_SIZE - + Data::BUMP_SEED_SIZE - + Data::TRX_COUNT_SIZE - + Data::BALANCE_SIZE - + Data::GENERATION_SIZE - + Data::CODE_SIZE_SIZE - + Data::RW_BLOCKED_SIZE; - - /// Deserialize `AccountV3` struct from input data - #[must_use] - fn unpack(input: &[u8]) -> Self { - let data = array_ref![input, 0, Data::SIZE]; - #[allow(clippy::ptr_offset_with_cast)] - let (address, bump_seed, trx_count, balance, generation, code_size, rw_blocked) = array_refs![ - data, - Data::ADDRESS_SIZE, - Data::BUMP_SEED_SIZE, - Data::TRX_COUNT_SIZE, - Data::BALANCE_SIZE, - Data::GENERATION_SIZE, - Data::CODE_SIZE_SIZE, - Data::RW_BLOCKED_SIZE - ]; - - Self { - address: Address::from(*address), - bump_seed: bump_seed[0], - trx_count: u64::from_le_bytes(*trx_count), - balance: U256::from_le_bytes(*balance), - generation: u32::from_le_bytes(*generation), - code_size: u32::from_le_bytes(*code_size), - rw_blocked: rw_blocked[0] != 0, - } - } - - /// Serialize `AccountV3` struct into given destination - fn pack(&self, dst: &mut [u8]) { - let data = array_mut_ref![dst, 0, Data::SIZE]; - #[allow(clippy::ptr_offset_with_cast)] - let (address, bump_seed, trx_count, balance, generation, code_size, rw_blocked) = mut_array_refs![ - data, - Data::ADDRESS_SIZE, - Data::BUMP_SEED_SIZE, - Data::TRX_COUNT_SIZE, - Data::BALANCE_SIZE, - Data::GENERATION_SIZE, - Data::CODE_SIZE_SIZE, - Data::RW_BLOCKED_SIZE - ]; - - *address = self.address.into(); - bump_seed[0] = self.bump_seed; - *trx_count = self.trx_count.to_le_bytes(); - *balance = self.balance.to_le_bytes(); - *generation = self.generation.to_le_bytes(); - *code_size = self.code_size.to_le_bytes(); - rw_blocked[0] = u8::from(self.rw_blocked); - } -} - -impl<'a> EthereumAccount<'a> { - pub fn check_blocked(&self) -> ProgramResult { - if self.rw_blocked { - // error message is parsed in proxy, do not change - return Err!(ProgramError::InvalidAccountData; "trying to execute transaction on rw locked account {}", self.address); - } - - Ok(()) - } - - pub fn create_account( - system_program: &System<'a>, - program_id: &Pubkey, - operator: &Operator<'a>, - address: &Address, - info: &'a AccountInfo<'a>, - bump_seed: u8, - space: usize, - ) -> ProgramResult { - if space < EthereumAccount::SIZE { - return Err!( - ProgramError::AccountDataTooSmall; - "Account {} - account space must be not less than minimal size of {} bytes", - address, - EthereumAccount::SIZE - ); - } - - let program_seeds: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], address.as_bytes(), &[bump_seed]]; - system_program.create_pda_account(program_id, operator, info, program_seeds, space)?; - - Ok(()) - } - - pub fn create_and_init_account( - system_program: &System<'a>, - program_id: &Pubkey, - operator: &Operator<'a>, - address: Address, - info: &'a AccountInfo<'a>, - bump_seed: u8, - space: usize, - ) -> ProgramResult { - Self::create_account( - system_program, - program_id, - operator, - &address, - info, - bump_seed, - space, - )?; - - EthereumAccount::init( - program_id, - info, - Data { - address, - bump_seed, - ..Default::default() - }, - )?; - - Ok(()) - } -} diff --git a/evm_loader/program/src/account/ether_balance.rs b/evm_loader/program/src/account/ether_balance.rs new file mode 100644 index 000000000..44de0c590 --- /dev/null +++ b/evm_loader/program/src/account/ether_balance.rs @@ -0,0 +1,288 @@ +use std::mem::size_of; + +use crate::{ + account::{TAG_ACCOUNT_CONTRACT, TAG_EMPTY}, + account_storage::KeysCache, + config::DEFAULT_CHAIN_ID, + error::{Error, Result}, + types::Address, +}; +use ethnum::U256; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey, rent::Rent, system_program}; + +use super::{ + AccountHeader, AccountsDB, ACCOUNT_PREFIX_LEN, ACCOUNT_SEED_VERSION, TAG_ACCOUNT_BALANCE, +}; + +#[repr(C, packed)] +pub struct HeaderV0 { + pub address: Address, + pub chain_id: u64, + pub trx_count: u64, + pub balance: U256, +} +impl AccountHeader for HeaderV0 { + const VERSION: u8 = 0; +} + +#[repr(C, packed)] +pub struct HeaderWithRevision { + pub v0: HeaderV0, + pub revision: u32, +} + +impl AccountHeader for HeaderWithRevision { + const VERSION: u8 = 2; +} + +// Set the last version of the Header struct here +// and change the `header_size` and `header_upgrade` functions +pub type Header = HeaderWithRevision; + +#[derive(Clone)] +pub struct BalanceAccount<'a> { + account: AccountInfo<'a>, +} + +impl<'a> BalanceAccount<'a> { + #[must_use] + pub fn required_account_size() -> usize { + ACCOUNT_PREFIX_LEN + size_of::
() + } + + #[must_use] + pub fn required_header_realloc(&self) -> usize { + let allocated_header_size = self.header_size(); + size_of::
().saturating_sub(allocated_header_size) + } + + pub fn from_account(program_id: &Pubkey, account: AccountInfo<'a>) -> Result { + super::validate_tag(program_id, &account, TAG_ACCOUNT_BALANCE)?; + + Ok(Self { account }) + } + + #[must_use] + pub fn info(&self) -> &AccountInfo<'a> { + &self.account + } + + pub fn create( + address: Address, + chain_id: u64, + accounts: &AccountsDB<'a>, + keys: Option<&KeysCache>, + rent: &Rent, + ) -> Result { + let (pubkey, bump_seed) = keys.map_or_else( + || address.find_balance_address(&crate::ID, chain_id), + |keys| keys.balance_with_bump_seed(&crate::ID, address, chain_id), + ); + + // Already created. Return immidiately + let account = accounts.get(&pubkey).clone(); + if !system_program::check_id(account.owner) { + let balance_account = Self::from_account(&crate::ID, account)?; + assert_eq!(balance_account.address(), address); + assert_eq!(balance_account.chain_id(), chain_id); + + return Ok(balance_account); + } + + if chain_id == DEFAULT_CHAIN_ID { + // Make sure no legacy account exists + let legacy_pubkey = keys.map_or_else( + || address.find_solana_address(&crate::ID).0, + |keys| keys.contract(&crate::ID, address), + ); + + let legacy_account = accounts.get(&legacy_pubkey); + if crate::check_id(legacy_account.owner) { + let legacy_tag = super::tag(&crate::ID, legacy_account)?; + assert!(legacy_tag == TAG_EMPTY || legacy_tag == TAG_ACCOUNT_CONTRACT); + } + } + + // Create a new account + let program_seeds: &[&[u8]] = &[ + &[ACCOUNT_SEED_VERSION], + address.as_bytes(), + &U256::from(chain_id).to_be_bytes(), + &[bump_seed], + ]; + + let system = accounts.system(); + let operator = accounts.operator(); + + system.create_pda_account( + &crate::ID, + operator, + &account, + program_seeds, + ACCOUNT_PREFIX_LEN + size_of::
(), + rent, + )?; + + Self::initialize(account, &crate::ID, address, chain_id) + } + + pub fn initialize( + account: AccountInfo<'a>, + program_id: &Pubkey, + address: Address, + chain_id: u64, + ) -> Result { + super::set_tag(program_id, &account, TAG_ACCOUNT_BALANCE, Header::VERSION)?; + { + let mut header = super::header_mut::
(&account); + header.v0.address = address; + header.v0.chain_id = chain_id; + header.v0.trx_count = 0; + header.v0.balance = U256::ZERO; + header.revision = 1; + } + + Ok(Self { account }) + } + + fn header_size(&self) -> usize { + match super::header_version(&self.account) { + 0 | 1 => size_of::(), + HeaderWithRevision::VERSION => size_of::(), + _ => panic!("Unknown header version"), + } + } + + fn header_upgrade(&mut self, rent: &Rent, db: &AccountsDB<'a>) -> Result<()> { + match super::header_version(&self.account) { + 0 | 1 => { + super::expand_header::(&self.account, rent, db)?; + } + HeaderWithRevision::VERSION => { + super::expand_header::(&self.account, rent, db)?; + } + _ => panic!("Unknown header version"), + } + + Ok(()) + } + + #[must_use] + pub fn pubkey(&self) -> &'a Pubkey { + self.account.key + } + + #[must_use] + pub fn address(&self) -> Address { + let header = super::header::(&self.account); + header.address + } + + #[must_use] + pub fn chain_id(&self) -> u64 { + let header = super::header::(&self.account); + header.chain_id + } + + #[must_use] + pub fn nonce(&self) -> u64 { + let header = super::header::(&self.account); + header.trx_count + } + + pub fn override_nonce_by(&mut self, value: u64) { + let mut header = super::header_mut::(&self.account); + header.trx_count = value; + } + + pub fn override_balance_by(&mut self, value: U256) { + let mut header = super::header_mut::(&self.account); + header.balance = value; + } + + #[must_use] + pub fn exists(&self) -> bool { + let header = super::header::(&self.account); + + ({ header.trx_count } > 0) || ({ header.balance } > 0) + } + + pub fn increment_nonce(&mut self) -> Result<()> { + self.increment_nonce_by(1) + } + + pub fn increment_nonce_by(&mut self, value: u64) -> Result<()> { + let mut header = super::header_mut::(&self.account); + + header.trx_count = header + .trx_count + .checked_add(value) + .ok_or_else(|| Error::NonceOverflow(header.address))?; + + Ok(()) + } + + #[must_use] + pub fn balance(&self) -> U256 { + let header = super::header::(&self.account); + header.balance + } + + pub fn transfer(&mut self, target: &mut BalanceAccount, value: U256) -> Result<()> { + if self.account.key == target.account.key { + return Ok(()); + } + + assert_eq!(self.chain_id(), target.chain_id()); + + self.burn(value)?; + target.mint(value) + } + + pub fn burn(&mut self, value: U256) -> Result<()> { + let mut header = super::header_mut::(&self.account); + + header.balance = header + .balance + .checked_sub(value) + .ok_or(Error::InsufficientBalance( + header.address, + header.chain_id, + value, + ))?; + + Ok(()) + } + + pub fn mint(&mut self, value: U256) -> Result<()> { + let mut header = super::header_mut::(&self.account); + + header.balance = header + .balance + .checked_add(value) + .ok_or(Error::IntegerOverflow)?; + + Ok(()) + } + + #[must_use] + pub fn revision(&self) -> u32 { + if super::header_version(&self.account) < HeaderWithRevision::VERSION { + return 0; + } + + let header = super::header::(&self.account); + header.revision + } + + pub fn increment_revision(&mut self, rent: &Rent, db: &AccountsDB<'a>) -> Result<()> { + if super::header_version(&self.account) < HeaderWithRevision::VERSION { + self.header_upgrade(rent, db)?; + } + + let mut header = super::header_mut::(&self.account); + header.revision = header.revision.wrapping_add(1); + + Ok(()) + } +} diff --git a/evm_loader/program/src/account/ether_contract.rs b/evm_loader/program/src/account/ether_contract.rs index 96b20bed3..40919abd2 100644 --- a/evm_loader/program/src/account/ether_contract.rs +++ b/evm_loader/program/src/account/ether_contract.rs @@ -1,90 +1,324 @@ -use std::cell::RefMut; -use std::mem::size_of; -use std::ops::Range; +use crate::{ + account::TAG_EMPTY, + account_storage::KeysCache, + error::{Error, Result}, + types::Address, +}; +use solana_program::{ + account_info::AccountInfo, entrypoint::MAX_PERMITTED_DATA_INCREASE, pubkey::Pubkey, rent::Rent, + system_program, +}; +use std::{ + cell::{Ref, RefMut}, + mem::size_of, +}; -use ethnum::U256; - -use crate::account::EthereumAccount; use crate::config::STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT; -const INTERNAL_STORAGE_SIZE: usize = - size_of::() * STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as usize; +use super::{ + AccountHeader, AccountsDB, ACCOUNT_PREFIX_LEN, ACCOUNT_SEED_VERSION, TAG_ACCOUNT_CONTRACT, +}; + +#[derive(Eq, PartialEq)] +pub enum AllocateResult { + Ready, + NeedMore, +} + +#[repr(C, packed)] +pub struct HeaderV0 { + pub address: Address, + pub chain_id: u64, + pub generation: u32, +} + +impl AccountHeader for HeaderV0 { + const VERSION: u8 = 0; +} + +#[repr(C, packed)] +pub struct HeaderWithRevision { + pub v0: HeaderV0, + pub revision: u32, +} + +impl AccountHeader for HeaderWithRevision { + const VERSION: u8 = 2; +} + +// Set the last version of the Header struct here +// and change the `header_size` and `header_upgrade` functions +pub type Header = HeaderWithRevision; + +pub type Storage = [[u8; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT]; +pub type Code = [u8]; -pub struct ContractData<'this, 'acc> { - account: &'this EthereumAccount<'acc>, +pub struct ContractAccount<'a> { + account: AccountInfo<'a>, } -impl<'acc> ContractData<'_, 'acc> { +impl<'a> ContractAccount<'a> { + #[must_use] + pub fn required_account_size(code: &[u8]) -> usize { + ACCOUNT_PREFIX_LEN + size_of::
() + size_of::() + code.len() + } + + #[must_use] + pub fn required_header_realloc(&self) -> usize { + let allocated_header_size = self.header_size(); + size_of::
().saturating_sub(allocated_header_size) + } + + pub fn from_account(program_id: &Pubkey, account: AccountInfo<'a>) -> Result { + super::validate_tag(program_id, &account, TAG_ACCOUNT_CONTRACT)?; + + Ok(Self { account }) + } + + #[must_use] + pub fn info(&self) -> &AccountInfo<'a> { + &self.account + } + + pub fn allocate( + address: Address, + code: &[u8], + rent: &Rent, + accounts: &AccountsDB, + keys: Option<&KeysCache>, + ) -> Result { + let (pubkey, bump_seed) = keys.map_or_else( + || address.find_solana_address(&crate::ID), + |keys| keys.contract_with_bump_seed(&crate::ID, address), + ); + + let info = accounts.get(&pubkey); + + let required_size = Self::required_account_size(code); + if info.data_len() >= required_size { + return Ok(AllocateResult::Ready); + } + + let system = accounts.system(); + let operator = accounts.operator(); + + if system_program::check_id(info.owner) { + let seeds: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], address.as_bytes(), &[bump_seed]]; + let space = required_size.min(MAX_PERMITTED_DATA_INCREASE); + system.create_pda_account(&crate::ID, operator, info, seeds, space, rent)?; + } else if crate::check_id(info.owner) { + super::validate_tag(&crate::ID, info, TAG_EMPTY)?; + + let max_size = info.data_len() + MAX_PERMITTED_DATA_INCREASE; + let space = required_size.min(max_size); + info.realloc(space, false)?; + + let required_balance = rent.minimum_balance(space); + if info.lamports() < required_balance { + let lamports = required_balance - info.lamports(); + system.transfer(operator, info, lamports)?; + } + } else { + return Err(Error::AccountInvalidOwner(pubkey, system_program::ID)); + } + + if info.data_len() >= required_size { + Ok(AllocateResult::Ready) + } else { + Ok(AllocateResult::NeedMore) + } + } + + pub fn create( + address: Address, + chain_id: u64, + generation: u32, + code: &[u8], + accounts: &AccountsDB<'a>, + keys: Option<&KeysCache>, + ) -> Result { + let (pubkey, _) = keys.map_or_else( + || address.find_solana_address(&crate::ID), + |keys| keys.contract_with_bump_seed(&crate::ID, address), + ); + + let account = accounts.get(&pubkey).clone(); + Self::initialize(account, &crate::ID, address, chain_id, generation, code) + } + + pub fn initialize( + account: AccountInfo<'a>, + program_id: &Pubkey, + address: Address, + chain_id: u64, + generation: u32, + code: &[u8], + ) -> Result { + super::validate_tag(program_id, &account, TAG_EMPTY)?; + super::set_tag(program_id, &account, TAG_ACCOUNT_CONTRACT, Header::VERSION)?; + + { + let mut header = super::header_mut::
(&account); + header.v0.address = address; + header.v0.chain_id = chain_id; + header.v0.generation = generation; + header.revision = 1; + } + + let mut contract = Self::from_account(program_id, account)?; + { + let mut contract_code = contract.code_mut(); + contract_code.copy_from_slice(code); + } + + Ok(contract) + } + #[must_use] - pub fn code(&self) -> RefMut<'acc, [u8]> { - let offset = INTERNAL_STORAGE_SIZE; - let len = self.account.code_size as usize; + pub fn pubkey(&self) -> &'a Pubkey { + self.account.key + } + + fn header_size(&self) -> usize { + match super::header_version(&self.account) { + 0 | 1 => size_of::(), + HeaderWithRevision::VERSION => size_of::(), + _ => panic!("Unknown header version"), + } + } + + fn header_upgrade(&mut self, rent: &Rent, db: &AccountsDB<'a>) -> Result<()> { + match super::header_version(&self.account) { + 0 | 1 => { + super::expand_header::(&self.account, rent, db)?; + } + HeaderWithRevision::VERSION => { + super::expand_header::(&self.account, rent, db)?; + } + _ => panic!("Unknown header version"), + } + + Ok(()) + } - self.extension_part_borrow_mut(offset, len) + #[inline] + #[must_use] + fn storage_offset(&self) -> usize { + ACCOUNT_PREFIX_LEN + self.header_size() } + #[inline] #[must_use] - pub fn storage(&self) -> RefMut<'acc, [u8]> { - let offset = 0; - let len = INTERNAL_STORAGE_SIZE; + pub fn storage(&self) -> Ref { + let offset = self.storage_offset(); + super::section(&self.account, offset) + } - self.extension_part_borrow_mut(offset, len) + #[inline] + fn storage_mut(&mut self) -> RefMut { + let offset = self.storage_offset(); + super::section_mut(&self.account, offset) } + #[inline] #[must_use] - pub fn extension_borrow_mut(&self) -> RefMut<'acc, [u8]> { - RefMut::map(self.account.info.data.borrow_mut(), |slice| { - &mut slice[EthereumAccount::SIZE..] - }) + fn code_offset(&self) -> usize { + self.storage_offset() + size_of::() } + #[inline] #[must_use] - fn extension_part_borrow_mut(&self, offset: usize, len: usize) -> RefMut<'acc, [u8]> { - RefMut::map(self.extension_borrow_mut(), |slice| { - &mut slice[offset..][..len] - }) + pub fn code(&self) -> Ref { + let offset = self.code_offset(); + + let data = self.account.data.borrow(); + Ref::map(data, |d| &d[offset..]) + } + + #[inline] + fn code_mut(&mut self) -> RefMut { + let offset = self.code_offset(); + + let data = self.account.data.borrow_mut(); + RefMut::map(data, |d| &mut d[offset..]) } -} -impl<'this, 'acc> EthereumAccount<'acc> { #[must_use] - pub fn is_contract(&self) -> bool { - self.code_size() != 0 + pub fn code_buffer(&self) -> crate::evm::Buffer { + let begin = self.code_offset(); + let end = begin + self.code_len(); + + unsafe { crate::evm::Buffer::from_account(&self.account, begin..end) } } #[must_use] - pub fn code_size(&self) -> usize { - self.code_size as usize + pub fn code_len(&self) -> usize { + let offset = self.code_offset(); + + self.account.data_len().saturating_sub(offset) } #[must_use] - pub fn code_location(&self) -> Range { - let begin = Self::SIZE + INTERNAL_STORAGE_SIZE; - let end = begin.saturating_add(self.code_size()); + pub fn address(&self) -> Address { + let header = super::header::(&self.account); + header.address + } - begin..end + #[must_use] + pub fn chain_id(&self) -> u64 { + let header = super::header::(&self.account); + header.chain_id } #[must_use] - pub fn contract_data(&'this self) -> Option> { - if !self.is_contract() { - return None; - } - Some(ContractData { account: self }) + pub fn generation(&self) -> u32 { + let header = super::header::(&self.account); + header.generation } #[must_use] - pub fn space_needed(code_size: usize) -> usize { - EthereumAccount::SIZE - + if code_size > 0 { - code_size + INTERNAL_STORAGE_SIZE - } else { - 0 - } + pub fn revision(&self) -> u32 { + if super::header_version(&self.account) < HeaderWithRevision::VERSION { + return 0; + } + + let header = super::header::(&self.account); + header.revision + } + + pub fn increment_revision(&mut self, rent: &Rent, db: &AccountsDB<'a>) -> Result<()> { + if super::header_version(&self.account) < HeaderWithRevision::VERSION { + self.header_upgrade(rent, db)?; + } + + let mut header = super::header_mut::(&self.account); + header.revision = header.revision.wrapping_add(1); + + Ok(()) } #[must_use] - pub fn size(&self) -> usize { - Self::space_needed(self.code_size()) + pub fn storage_value(&self, index: usize) -> [u8; 32] { + assert!(index < STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT); + + let storage = self.storage(); + storage[index] + } + + pub fn set_storage_value(&mut self, index: usize, value: &[u8; 32]) { + assert!(index < STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT); + + let mut storage = self.storage_mut(); + + let cell: &mut [u8; 32] = &mut storage[index]; + cell.copy_from_slice(value); + } + + pub fn set_storage_multiple_values(&mut self, offset: usize, values: &[[u8; 32]]) { + let max = offset.saturating_add(values.len()); + assert!(max <= STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT); + + let mut storage = self.storage_mut(); + storage[offset..][..values.len()].copy_from_slice(values); } } diff --git a/evm_loader/program/src/account/ether_storage.rs b/evm_loader/program/src/account/ether_storage.rs index 4c28e2969..cdd216d6a 100644 --- a/evm_loader/program/src/account/ether_storage.rs +++ b/evm_loader/program/src/account/ether_storage.rs @@ -1,58 +1,19 @@ -#![allow(clippy::use_self)] // Can't use generic parameter from outer function +use std::cell::{Ref, RefMut}; +use std::mem::size_of; -use super::{program, EthereumAccount, EthereumStorage, Operator, Packable}; -use crate::types::Address; -use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; +use super::{AccountHeader, AccountsDB, NoHeader, ACCOUNT_PREFIX_LEN, TAG_EMPTY, TAG_STORAGE_CELL}; +use crate::error::Result; use ethnum::U256; -use solana_program::{ - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, rent::Rent, - sysvar::Sysvar, -}; - -/// Ethereum storage data account -#[derive(Default, Debug)] -pub struct Data { - pub address: Address, - pub generation: u32, - pub index: U256, -} - -impl Packable for Data { - /// Storage struct tag - const TAG: u8 = super::TAG_CONTRACT_STORAGE; - /// Storage struct serialized size - const SIZE: usize = 20 + 4 + 32; - - /// Deserialize `Storage` struct from input data - #[must_use] - fn unpack(input: &[u8]) -> Self { - let data = array_ref![input, 0, Data::SIZE]; - let (address, generation, index) = array_refs![data, 20, 4, 32]; - - Self { - address: Address(*address), - generation: u32::from_le_bytes(*generation), - index: U256::from_le_bytes(*index), - } - } - - /// Serialize `Storage` struct into given destination - fn pack(&self, output: &mut [u8]) { - let data = array_mut_ref![output, 0, Data::SIZE]; - let (address, generation, index) = mut_array_refs![data, 20, 4, 32]; - - *address = *self.address.as_bytes(); - *generation = self.generation.to_le_bytes(); - *index = self.index.to_le_bytes(); - } -} +use solana_program::{account_info::AccountInfo, pubkey::Pubkey, rent::Rent}; -pub struct EthereumStorageAddress { +#[derive(Copy, Clone)] +pub struct StorageCellAddress { + base: Pubkey, seed: [u8; 32], pubkey: Pubkey, } -impl EthereumStorageAddress { +impl StorageCellAddress { #[must_use] fn make_seed(index: &U256) -> [u8; 32] { let mut buffer = [0_u8; 32]; @@ -89,6 +50,7 @@ impl EthereumStorageAddress { let pubkey = Pubkey::create_with_seed(base, seed, program_id).unwrap(); Self { + base: *base, seed: seed_buffer, pubkey, } @@ -105,122 +67,238 @@ impl EthereumStorageAddress { } } -impl<'a> EthereumStorage<'a> { +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct Cell { + pub subindex: u8, + pub value: [u8; 32], +} + +pub struct StorageCell<'a> { + account: AccountInfo<'a>, +} + +#[repr(C, packed)] +pub struct HeaderWithRevision { + revision: u32, +} +impl AccountHeader for HeaderWithRevision { + const VERSION: u8 = 2; +} + +// Set the last version of the Header struct here +// and change the `header_size` and `header_upgrade` functions +pub type Header = HeaderWithRevision; + +impl<'a> StorageCell<'a> { #[must_use] - pub fn get(&self, subindex: u8) -> [u8; 32] { - let data = self.info.data.borrow(); - let data = &data[Self::SIZE..]; + pub fn required_account_size(cells: usize) -> usize { + ACCOUNT_PREFIX_LEN + size_of::
() + cells * size_of::() + } - for chunk in data.chunks_exact(1 + 32) { - if chunk[0] != subindex { - continue; - } + #[must_use] + pub fn required_header_realloc(&self) -> usize { + let allocated_header_size = self.header_size(); + size_of::
().saturating_sub(allocated_header_size) + } - return chunk[1..].try_into().unwrap(); - } + pub fn from_account(program_id: &Pubkey, account: AccountInfo<'a>) -> Result { + super::validate_tag(program_id, &account, TAG_STORAGE_CELL)?; - [0_u8; 32] + Ok(Self { account }) } - pub fn set( - &mut self, - subindex: u8, - value: &[u8; 32], - required_account_transfers: &mut std::collections::HashMap, u64)>, - ) -> Result<(), ProgramError> { - { - let mut data = self.info.data.borrow_mut(); - let data = &mut data[Self::SIZE..]; + #[must_use] + pub fn info(&self) -> &AccountInfo<'a> { + &self.account + } - for chunk in data.chunks_exact_mut(1 + 32) { - if chunk[0] != subindex { - continue; - } + pub fn create( + address: StorageCellAddress, + allocate_cells: usize, + accounts: &AccountsDB<'a>, + signer_seeds: &[&[u8]], + rent: &Rent, + ) -> Result { + let base_account = accounts.get(&address.base); + let cell_account = accounts.get(&address.pubkey); - chunk[1..].copy_from_slice(value); + assert!(allocate_cells <= u8::MAX.into()); + let space = Self::required_account_size(allocate_cells); - return Ok(()); - } - } // drop `data` + let system = accounts.system(); - let new_len = self.info.data_len() + 1 + 32; // new_len <= 8.25 kb - self.info.realloc(new_len, false)?; + system.create_account_with_seed( + &crate::ID, + accounts.operator(), + base_account, + signer_seeds, + cell_account, + address.seed(), + space, + rent, + )?; - let minimum_balance = Rent::get()?.minimum_balance(new_len); - if self.info.lamports() < minimum_balance { - let required_lamports = minimum_balance - self.info.lamports(); + Self::initialize(cell_account.clone(), &crate::ID) + } - // Accumulate the amount of lamports we need to transfer for this account - required_account_transfers - .insert(*self.info.key, (Clone::clone(self.info), required_lamports)); + pub fn initialize(account: AccountInfo<'a>, program_id: &Pubkey) -> Result { + super::validate_tag(program_id, &account, TAG_EMPTY)?; + super::set_tag(program_id, &account, TAG_STORAGE_CELL, Header::VERSION)?; + { + let mut header = super::header_mut::
(&account); + header.revision = 1; } - let mut data = self.info.data.borrow_mut(); - let data = &mut data[1..]; // skip tag + Ok(Self { account }) + } + + #[must_use] + pub fn pubkey(&self) -> &'a Pubkey { + self.account.key + } - let chunk_start = data.len() - 1 - 32; - let chunk = &mut data[chunk_start..]; + fn header_size(&self) -> usize { + match super::header_version(&self.account) { + 0 | 1 => size_of::(), + HeaderWithRevision::VERSION => size_of::(), + _ => panic!("Unknown header version"), + } + } - chunk[0] = subindex; - chunk[1..].copy_from_slice(value); + fn header_upgrade(&mut self, rent: &Rent, db: &AccountsDB<'a>) -> Result<()> { + match super::header_version(&self.account) { + 0 | 1 => { + super::expand_header::(&self.account, rent, db)?; + } + HeaderWithRevision::VERSION => { + super::expand_header::(&self.account, rent, db)?; + } + _ => panic!("Unknown header version"), + } Ok(()) } - #[allow(clippy::too_many_arguments)] - pub fn create( - contract: &EthereumAccount<'a>, - storage_account: &'a AccountInfo<'a>, - storage_address: &EthereumStorageAddress, - index: U256, - subindex: u8, - value: &[u8; 32], - operator: &Operator<'a>, - system: &program::System<'a>, - ) -> Result { - let space = Self::SIZE + 1 + 32; + fn cells_offset(&self) -> usize { + ACCOUNT_PREFIX_LEN + self.header_size() + } - system.create_account_with_seed( - operator, - contract, - contract.info.owner, - storage_account, - storage_address.seed(), - space, - )?; + #[must_use] + pub fn cells(&self) -> Ref<[Cell]> { + let cells_offset = self.cells_offset(); - let storage = Self::init( - contract.info.owner, - storage_account, - Data { - address: contract.address, - generation: contract.generation, - index, - }, - )?; + let data = self.account.data.borrow(); + let data = Ref::map(data, |d| &d[cells_offset..]); + + Ref::map(data, |bytes| { + static_assertions::assert_eq_align!(Cell, u8); + assert_eq!(bytes.len() % size_of::(), 0); + + // SAFETY: Cell has the same alignment as bytes + unsafe { + let ptr = bytes.as_ptr().cast::(); + let len = bytes.len() / size_of::(); + std::slice::from_raw_parts(ptr, len) + } + }) + } + + #[must_use] + pub fn cells_mut(&mut self) -> RefMut<[Cell]> { + let cells_offset = self.cells_offset(); - let mut data = storage_account.data.borrow_mut(); - let data = &mut data[Self::SIZE..]; + let data = self.account.data.borrow_mut(); + let data = RefMut::map(data, |d| &mut d[cells_offset..]); - data[0] = subindex; - data[1..].copy_from_slice(value); + RefMut::map(data, |bytes| { + static_assertions::assert_eq_align!(Cell, u8); + assert_eq!(bytes.len() % size_of::(), 0); - Ok(storage) + // SAFETY: Cell has the same alignment as bytes + unsafe { + let ptr = bytes.as_mut_ptr().cast::(); + let len = bytes.len() / size_of::(); + std::slice::from_raw_parts_mut(ptr, len) + } + }) } - pub fn clear(&mut self, generation: u32, operator: &Operator<'a>) -> Result<(), ProgramError> { - self.generation = generation; + #[must_use] + pub fn get(&self, subindex: u8) -> [u8; 32] { + for cell in &*self.cells() { + if cell.subindex != subindex { + continue; + } + + return cell.value; + } + + [0_u8; 32] + } - self.info.realloc(Self::SIZE, false)?; + pub fn update(&mut self, subindex: u8, value: &[u8; 32]) -> Result<()> { + // todo: if value is zero - destroy cell - let minimum_balance = Rent::get()?.minimum_balance(Self::SIZE); - let excessive_lamports = self.info.lamports().saturating_sub(minimum_balance); + for cell in &mut *self.cells_mut() { + if cell.subindex != subindex { + continue; + } - if excessive_lamports > 0 { - **self.info.lamports.borrow_mut() -= excessive_lamports; - **operator.lamports.borrow_mut() += excessive_lamports; + cell.value.copy_from_slice(value); + return Ok(()); } + let new_len = self.account.data_len() + size_of::(); // new_len <= 8.25 kb + self.account.realloc(new_len, false)?; + + let mut cells = self.cells_mut(); + + let last_cell = cells.last_mut().unwrap(); + last_cell.subindex = subindex; + last_cell.value.copy_from_slice(value); + + Ok(()) + } + + pub fn sync_lamports(&mut self, rent: &Rent, accounts: &AccountsDB<'a>) -> Result<()> { + let original_data_len = unsafe { self.account.original_data_len() }; + if original_data_len == self.account.data_len() { + return Ok(()); + } + + let minimum_balance = rent.minimum_balance(self.account.data_len()); + if self.account.lamports() >= minimum_balance { + return Ok(()); + } + + let system = accounts.system(); + let operator = accounts.operator(); + + let lamports = minimum_balance - self.account.lamports(); + system.transfer(operator, &self.account, lamports)?; + + Ok(()) + } + + #[must_use] + pub fn revision(&self) -> u32 { + if super::header_version(&self.account) < HeaderWithRevision::VERSION { + return 0; + } + + let header = super::header::(&self.account); + header.revision + } + + pub fn increment_revision(&mut self, rent: &Rent, accounts: &AccountsDB<'a>) -> Result<()> { + if super::header_version(&self.account) < HeaderWithRevision::VERSION { + self.header_upgrade(rent, accounts)?; + } + + let mut header = super::header_mut::(&self.account); + header.revision = header.revision.wrapping_add(1); + Ok(()) } } diff --git a/evm_loader/program/src/account/holder.rs b/evm_loader/program/src/account/holder.rs index 268d02803..c81cdd3f9 100644 --- a/evm_loader/program/src/account/holder.rs +++ b/evm_loader/program/src/account/holder.rs @@ -1,102 +1,274 @@ -#![allow(clippy::use_self)] // Can't use generic parameter from outer function - -use std::cell::Ref; - -use arrayref::{array_mut_ref, array_ref}; -use arrayref::{array_refs, mut_array_refs}; +use linked_list_allocator::Heap; +use solana_program::account_info::AccountInfo; use solana_program::pubkey::Pubkey; +use static_assertions::const_assert; +use std::cell::{Ref, RefMut}; +use std::mem::{align_of, size_of}; +use crate::account::TAG_STATE_FINALIZED; +use crate::allocator::STATE_ACCOUNT_DATA_ADDRESS; use crate::error::{Error, Result}; use crate::types::Transaction; -use super::Holder; -use super::Operator; -use super::Packable; +use super::{AccountHeader, Operator, ACCOUNT_PREFIX_LEN, TAG_EMPTY, TAG_HOLDER}; /// Ethereum holder data account -#[derive(Default, Debug)] -pub struct Data { +#[repr(C, packed)] +pub struct Header { pub owner: Pubkey, pub transaction_hash: [u8; 32], pub transaction_len: usize, + pub heap_offset: usize, } -impl Packable for Data { - /// Holder struct tag - const TAG: u8 = super::TAG_HOLDER; - /// Holder struct serialized size - const SIZE: usize = 32 + 32 + 8; +impl AccountHeader for Header { + const VERSION: u8 = 0; +} - /// Deserialize `Holder` struct from input data - #[must_use] - fn unpack(src: &[u8]) -> Self { - let data = array_ref![src, 0, Data::SIZE]; - let (owner, hash, len) = array_refs![data, 32, 32, 8]; - - Self { - owner: Pubkey::new_from_array(*owner), - transaction_hash: *hash, - transaction_len: usize::from_le_bytes(*len), +pub struct Holder<'a> { + account: AccountInfo<'a>, +} + +const HEADER_OFFSET: usize = ACCOUNT_PREFIX_LEN; +pub const BUFFER_OFFSET: usize = HEADER_OFFSET + size_of::
(); + +// Offset of the Header.heap_offset from the start of account data. +// TODO: get rid of memoffset in favor of core::mem::offset_of! when rust version >= 1.77. +pub const HEAP_OFFSET_OFFSET: usize = HEADER_OFFSET + memoffset::offset_of!(Header, heap_offset); +// State Account Header and Holder Account Header should have a shared and fixed memory cell that denotes +// the offset of the persistent heap (heap_offset field). +// The following asert checks that State Account Header does not overlap with `heap_offset` memory cell, +// so writes to State Account Header do not override it. +const_assert!( + memoffset::offset_of!(Header, heap_offset) >= size_of::() +); + +impl<'a> Holder<'a> { + pub fn from_account(program_id: &Pubkey, account: AccountInfo<'a>) -> Result { + match super::tag(program_id, &account)? { + TAG_STATE_FINALIZED => { + super::set_tag(program_id, &account, TAG_HOLDER, Header::VERSION)?; + + let mut holder = Self { account }; + holder.clear(); + + Ok(holder) + } + TAG_HOLDER => Ok(Self { account }), + _ => Err(Error::AccountInvalidTag(*account.key, TAG_HOLDER)), + } + } + + pub fn create( + program_id: &Pubkey, + account: AccountInfo<'a>, + seed: &str, + operator: &Operator, + ) -> Result { + if account.owner != program_id { + return Err(Error::AccountInvalidOwner(*account.key, *program_id)); + } + + let key = Pubkey::create_with_seed(operator.key, seed, program_id)?; + if &key != account.key { + return Err(Error::AccountInvalidKey(*account.key, key)); } + + super::validate_tag(program_id, &account, TAG_EMPTY)?; + super::set_tag(&crate::ID, &account, TAG_HOLDER, Header::VERSION)?; + + let mut holder = Self::from_account(program_id, account)?; + holder.header_mut().owner = *operator.key; + holder.clear(); + + Ok(holder) + } + + pub fn update(&mut self, f: F) + where + F: FnOnce(&mut Header), + { + let mut header = self.header_mut(); + f(&mut header); } - /// Serialize `Holder` struct into given destination - fn pack(&self, dst: &mut [u8]) { - let data = array_mut_ref![dst, 0, Data::SIZE]; - let (owner, hash, len) = mut_array_refs![data, 32, 32, 8]; + fn header(&self) -> Ref
{ + super::section(&self.account, HEADER_OFFSET) + } - owner.copy_from_slice(self.owner.as_ref()); - hash.copy_from_slice(&self.transaction_hash); - *len = self.transaction_len.to_le_bytes(); + fn header_mut(&mut self) -> RefMut
{ + super::section_mut(&self.account, HEADER_OFFSET) } -} -impl<'a> Holder<'a> { - pub fn clear(&mut self) -> Result<()> { - self.transaction_hash.fill(0); - self.transaction_len = 0; + fn buffer(&self) -> Ref<[u8]> { + let data = self.account.data.borrow(); + Ref::map(data, |d| &d[BUFFER_OFFSET..]) + } - let mut data = self.info.try_borrow_mut_data()?; - data[Self::SIZE..].fill(0); + fn buffer_mut(&mut self) -> RefMut<[u8]> { + let data = self.account.data.borrow_mut(); + RefMut::map(data, |d| &mut d[BUFFER_OFFSET..]) + } - Ok(()) + pub fn clear(&mut self) { + { + let mut header = self.header_mut(); + header.transaction_hash.fill(0); + header.transaction_len = 0; + header.heap_offset = 0; + } + { + let mut buffer = self.buffer_mut(); + buffer.fill(0); + } } pub fn write(&mut self, offset: usize, bytes: &[u8]) -> Result<()> { - let mut data = self.info.try_borrow_mut_data()?; - - let begin = Self::SIZE - .checked_add(offset) - .ok_or(Error::IntegerOverflow)?; - let end = begin + let begin = offset; + let end = offset .checked_add(bytes.len()) .ok_or(Error::IntegerOverflow)?; - data[begin..end].copy_from_slice(bytes); - self.transaction_len = std::cmp::max(self.transaction_len, offset + bytes.len()); + { + let mut header = self.header_mut(); + header.transaction_len = std::cmp::max(header.transaction_len, end); + } + { + let mut buffer = self.buffer_mut(); + let Some(buffer) = buffer.get_mut(begin..end) else { + return Err(Error::HolderInsufficientSize(buffer.len(), end)); + }; + + buffer.copy_from_slice(bytes); + } Ok(()) } #[must_use] - pub fn transaction(&self) -> Ref<'a, [u8]> { - let data = Ref::map(self.info.data.borrow(), |d| *d); - Ref::map(data, |d| &d[Self::SIZE..][..self.transaction_len]) + pub fn transaction_len(&self) -> usize { + self.header().transaction_len + } + + #[must_use] + pub fn transaction(&self) -> Ref<[u8]> { + let len = self.transaction_len(); + + let buffer = self.buffer(); + Ref::map(buffer, |b| &b[..len]) + } + + #[must_use] + pub fn transaction_hash(&self) -> [u8; 32] { + self.header().transaction_hash + } + + pub fn update_transaction_hash(&mut self, hash: [u8; 32]) { + if self.transaction_hash() == hash { + return; + } + + self.clear(); + self.header_mut().transaction_hash = hash; + } + + #[must_use] + pub fn owner(&self) -> Pubkey { + self.header().owner } pub fn validate_owner(&self, operator: &Operator) -> Result<()> { - if &self.owner != operator.key { - return Err(Error::HolderInvalidOwner(self.owner, *operator.key)); + if &self.owner() != operator.key { + return Err(Error::HolderInvalidOwner(self.owner(), *operator.key)); } Ok(()) } pub fn validate_transaction(&self, trx: &Transaction) -> Result<()> { - if self.transaction_hash != trx.hash { - return Err(Error::HolderInvalidHash(self.transaction_hash, trx.hash)); + if self.transaction_hash() != trx.hash() { + return Err(Error::HolderInvalidHash( + self.transaction_hash(), + trx.hash(), + )); } Ok(()) } + + /// Initializes the heap using the whole account data space. + /// Also, writes the offset of the heap object into the separate field in the header. + /// After this, the persistent objects can be allocated into the account data. + pub fn init_heap(&mut self, transaction_offset: usize) -> Result<()> { + // For this case, the account.owner is already validated to be equal to program id. + Self::init_holder_heap(self.account.owner, &mut self.account, transaction_offset) + } + + /// Associated function, see `fn init_heap`. + pub fn init_holder_heap( + program_id: &Pubkey, + account: &mut AccountInfo, + transaction_offset: usize, + ) -> Result<()> { + // Validation: check that the passed account is a variant of Holder: Holder, State or StateFinalized. + // An additional owner check is happening inside the tag. + let tag = crate::account::tag(program_id, account)?; + assert!( + tag == TAG_HOLDER || tag == crate::account::TAG_STATE || tag == TAG_STATE_FINALIZED + ); + + let data_ptr = account.data.borrow().as_ptr(); + // Validation: the Holder Account used as a persistent heap, must be first in the account list. + assert_eq!(data_ptr as usize, STATE_ACCOUNT_DATA_ADDRESS); + + // Calculate the actual aligned heap object ptr and its offset. + let (heap_ptr, heap_object_offset) = { + // Locate heap object into the buffer with offset no less than min_heap_object_offset. + let mut heap_object_offset = BUFFER_OFFSET + transaction_offset; + let mut heap_ptr = data_ptr.wrapping_add(heap_object_offset); + + // Calculate alignment and offset the heap pointer. + let alignment = heap_ptr.align_offset(align_of::()); + heap_ptr = heap_ptr.wrapping_add(alignment); + heap_object_offset += alignment; + // Validation: double check the alignment. + assert_eq!(heap_ptr.align_offset(align_of::()), 0); + + (heap_ptr, heap_object_offset) + }; + + // Initialize the heap. + let heap_ptr = heap_ptr.cast_mut(); + unsafe { + // First, zero out underlying bytes of the future heap representation. + heap_ptr.write_bytes(0, size_of::()); + // Calculate the bottom of the heap, right after the Heap object. + let heap_bottom = heap_ptr.add(size_of::()); + + // Size of heap is equal to account data length minus the length of prefix. + let heap_size = account + .data_len() + .saturating_sub(heap_object_offset + size_of::()); + // Validation: check that heap object is within the account data. + assert!(heap_size > 0); + + // Cast to reference and init. + // Zeroed memory is a valid representation of the Heap and hence we can safely do it. + // That's a safety reason we zeroed the memory above. + #[allow(clippy::cast_ptr_alignment)] + let heap = &mut *(heap_ptr.cast::()); + heap.init(heap_bottom, heap_size); + }; + + // Write the actual heap offset into the header. This memory cell is used by the allocator. + super::section_mut::
(account, HEADER_OFFSET).heap_offset = heap_object_offset; + + Ok(()) + } + + /// # Safety + /// Permanently deletes Holder account and all data in it + pub unsafe fn suicide(self, operator: &Operator) { + crate::account::delete(&self.account, operator); + } } diff --git a/evm_loader/program/src/account/legacy/legacy_ether.rs b/evm_loader/program/src/account/legacy/legacy_ether.rs new file mode 100644 index 000000000..ea42bfff6 --- /dev/null +++ b/evm_loader/program/src/account/legacy/legacy_ether.rs @@ -0,0 +1,119 @@ +use std::mem::size_of; + +use ethnum::U256; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::{config::STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT, error::Result, types::Address}; + +pub struct LegacyEtherData { + /// Ethereum address + pub address: Address, + /// Solana account nonce + pub bump_seed: u8, + /// Ethereum account nonce + pub trx_count: u64, + /// Neon token balance + pub balance: U256, + /// Account generation, increment on suicide + pub generation: u32, + /// Contract code size + pub code_size: u32, + /// Read-write lock + pub rw_blocked: bool, +} + +impl LegacyEtherData { + const ADDRESS_SIZE: usize = size_of::
(); + const BUMP_SEED_SIZE: usize = size_of::(); + const TRX_COUNT_SIZE: usize = size_of::(); + const BALANCE_SIZE: usize = size_of::(); + const GENERATION_SIZE: usize = size_of::(); + const CODE_SIZE_SIZE: usize = size_of::(); + const RW_BLOCKED_SIZE: usize = size_of::(); + + /// `AccountV3` struct tag + pub const TAG: u8 = super::TAG_ACCOUNT_CONTRACT_DEPRECATED; + + /// `AccountV3` struct serialized size + pub const SIZE: usize = Self::ADDRESS_SIZE + + Self::BUMP_SEED_SIZE + + Self::TRX_COUNT_SIZE + + Self::BALANCE_SIZE + + Self::GENERATION_SIZE + + Self::CODE_SIZE_SIZE + + Self::RW_BLOCKED_SIZE; + + #[must_use] + pub fn unpack(input: &[u8]) -> Self { + let data = arrayref::array_ref![input, 0, LegacyEtherData::SIZE]; + #[allow(clippy::ptr_offset_with_cast)] + let (address, bump_seed, trx_count, balance, generation, code_size, rw_blocked) = arrayref::array_refs![ + data, + LegacyEtherData::ADDRESS_SIZE, + LegacyEtherData::BUMP_SEED_SIZE, + LegacyEtherData::TRX_COUNT_SIZE, + LegacyEtherData::BALANCE_SIZE, + LegacyEtherData::GENERATION_SIZE, + LegacyEtherData::CODE_SIZE_SIZE, + LegacyEtherData::RW_BLOCKED_SIZE + ]; + + Self { + address: Address::from(*address), + bump_seed: bump_seed[0], + trx_count: u64::from_le_bytes(*trx_count), + balance: U256::from_le_bytes(*balance), + generation: u32::from_le_bytes(*generation), + code_size: u32::from_le_bytes(*code_size), + rw_blocked: rw_blocked[0] != 0, + } + } + + pub fn from_account(program_id: &Pubkey, account: &AccountInfo) -> Result { + crate::account::validate_tag(program_id, account, Self::TAG)?; + + let data = account.try_borrow_data()?; + Ok(Self::unpack(&data[1..])) + } + + #[allow(clippy::unused_self)] + #[must_use] + pub fn read_storage(&self, account: &AccountInfo) -> Vec<[u8; 32]> { + if self.code_size == 0 { + return vec![[0; 32]; STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT]; + } + + let data = account.data.borrow(); + + let storage_offset = 1 + Self::SIZE; + let storage_len = 32 * STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT; + + let storage = &data[storage_offset..][..storage_len]; + let storage = unsafe { + // storage_len is multiple of STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT + // [u8; 32] has the same alignment as u8 + let ptr: *const [u8; 32] = storage.as_ptr().cast(); + std::slice::from_raw_parts(ptr, STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT) + }; + + storage.to_vec() + } + + #[must_use] + pub fn read_code(&self, account: &AccountInfo) -> Vec { + if self.code_size == 0 { + return Vec::new(); + } + + let data = account.data.borrow(); + + let storage_offset = 1 + Self::SIZE; + let storage_len = 32 * STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT; + + let code_offset = storage_offset + storage_len; + let code_len = self.code_size as usize; + + let code = &data[code_offset..][..code_len]; + code.to_vec() + } +} diff --git a/evm_loader/program/src/account/legacy/legacy_holder.rs b/evm_loader/program/src/account/legacy/legacy_holder.rs new file mode 100644 index 000000000..ee7e01524 --- /dev/null +++ b/evm_loader/program/src/account/legacy/legacy_holder.rs @@ -0,0 +1,70 @@ +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::error::Result; + +#[derive(Debug)] +pub struct LegacyHolderData { + pub owner: Pubkey, + pub transaction_hash: [u8; 32], + pub transaction_len: usize, +} + +#[derive(Debug)] +pub struct LegacyFinalizedData { + pub owner: Pubkey, + pub transaction_hash: [u8; 32], +} + +impl LegacyHolderData { + /// Holder struct tag + const TAG: u8 = super::TAG_HOLDER_DEPRECATED; + /// Holder struct serialized size + const SIZE: usize = 32 + 32 + 8; + + /// Deserialize `Holder` struct from input data + #[must_use] + fn unpack(src: &[u8]) -> Self { + let data = arrayref::array_ref![src, 0, LegacyHolderData::SIZE]; + let (owner, hash, len) = arrayref::array_refs![data, 32, 32, 8]; + + Self { + owner: Pubkey::new_from_array(*owner), + transaction_hash: *hash, + transaction_len: usize::from_le_bytes(*len), + } + } + + pub fn from_account(program_id: &Pubkey, account: &AccountInfo) -> Result { + crate::account::validate_tag(program_id, account, Self::TAG)?; + + let data = account.try_borrow_data()?; + Ok(Self::unpack(&data[1..])) + } +} + +impl LegacyFinalizedData { + /// Finalized storage struct tag + const TAG: u8 = super::TAG_STATE_FINALIZED_DEPRECATED; + /// Finalized storage struct serialized size + const SIZE: usize = 32 + 32; + + /// Deserialize `FinalizedState` struct from input data + #[must_use] + fn unpack(src: &[u8]) -> Self { + #[allow(clippy::use_self)] + let data = arrayref::array_ref![src, 0, LegacyFinalizedData::SIZE]; + let (owner, hash) = arrayref::array_refs![data, 32, 32]; + + Self { + owner: Pubkey::new_from_array(*owner), + transaction_hash: *hash, + } + } + + pub fn from_account(program_id: &Pubkey, account: &AccountInfo) -> Result { + crate::account::validate_tag(program_id, account, Self::TAG)?; + + let data = account.try_borrow_data()?; + Ok(Self::unpack(&data[1..])) + } +} diff --git a/evm_loader/program/src/account/legacy/legacy_storage_cell.rs b/evm_loader/program/src/account/legacy/legacy_storage_cell.rs new file mode 100644 index 000000000..7ca5e9a67 --- /dev/null +++ b/evm_loader/program/src/account/legacy/legacy_storage_cell.rs @@ -0,0 +1,78 @@ +use std::mem::size_of; + +use ethnum::U256; +use solana_program::account_info::AccountInfo; +use solana_program::pubkey::Pubkey; + +use crate::account::ether_storage::Cell; +use crate::error::Result; +use crate::types::Address; + +pub struct LegacyStorageData { + pub address: Address, + pub generation: u32, + pub index: U256, +} + +impl LegacyStorageData { + /// Storage struct tag + pub const TAG: u8 = super::TAG_STORAGE_CELL_DEPRECATED; + /// Storage struct serialized size + pub const SIZE: usize = 20 + 4 + 32; + + #[must_use] + pub fn unpack(input: &[u8]) -> Self { + let data = arrayref::array_ref![input, 0, LegacyStorageData::SIZE]; + let (address, generation, index) = arrayref::array_refs![data, 20, 4, 32]; + + Self { + address: Address(*address), + generation: u32::from_le_bytes(*generation), + index: U256::from_le_bytes(*index), + } + } + + pub fn from_account(program_id: &Pubkey, account: &AccountInfo) -> Result { + crate::account::validate_tag(program_id, account, Self::TAG)?; + + let data = account.try_borrow_data()?; + Ok(Self::unpack(&data[1..])) + } + + #[allow(clippy::unused_self)] + #[must_use] + pub fn read_cells(&self, account: &AccountInfo) -> Vec { + let cells_offset = 1 + Self::SIZE; + + let data = account.data.borrow(); + let data = &data[cells_offset..]; + + static_assertions::assert_eq_align!(Cell, u8); + assert_eq!(data.len() % size_of::(), 0); + + // SAFETY: Cell has the same alignment as data + let cells = unsafe { + let ptr = data.as_ptr().cast::(); + let len = data.len() / size_of::(); + std::slice::from_raw_parts(ptr, len) + }; + + cells.to_vec() + } + + #[allow(dead_code)] + #[must_use] + pub fn read_value(&self, subindex: u8, account: &AccountInfo) -> [u8; 32] { + let cells = self.read_cells(account); + + for cell in cells { + if cell.subindex != subindex { + continue; + } + + return cell.value; + } + + [0_u8; 32] + } +} diff --git a/evm_loader/program/src/account/legacy/mod.rs b/evm_loader/program/src/account/legacy/mod.rs new file mode 100644 index 000000000..58e5586a9 --- /dev/null +++ b/evm_loader/program/src/account/legacy/mod.rs @@ -0,0 +1,251 @@ +mod legacy_ether; +mod legacy_holder; +mod legacy_storage_cell; + +pub use legacy_ether::LegacyEtherData; +pub use legacy_holder::LegacyFinalizedData; +pub use legacy_holder::LegacyHolderData; +pub use legacy_storage_cell::LegacyStorageData; + +use solana_program::system_program; +use solana_program::{account_info::AccountInfo, rent::Rent, sysvar::Sysvar}; + +use super::AccountHeader; +use super::Holder; +use super::HolderHeader; +use super::StateFinalizedAccount; +use super::StateFinalizedHeader; +use super::StorageCellHeader; +use super::TAG_HOLDER; +use super::TAG_STATE_FINALIZED; +use super::{AccountsDB, ContractAccount, TAG_STORAGE_CELL}; +use crate::{ + account::{BalanceAccount, StorageCell}, + account_storage::KeysCache, + error::Result, +}; + +// First version +pub const TAG_STATE_DEPRECATED: u8 = 22; +pub const TAG_STATE_FINALIZED_DEPRECATED: u8 = 31; +pub const TAG_HOLDER_DEPRECATED: u8 = 51; +pub const TAG_ACCOUNT_CONTRACT_DEPRECATED: u8 = 12; +pub const TAG_STORAGE_CELL_DEPRECATED: u8 = 42; +// Before account revision (Holder and Finalized remain the same) +pub const TAG_STATE_BEFORE_REVISION: u8 = 23; + +fn reduce_account_size(account: &AccountInfo, required_len: usize, rent: &Rent) -> Result { + assert!(account.data_len() > required_len); + + account.realloc(required_len, false)?; + + // Return excessive lamports to the operator + let minimum_balance = rent.minimum_balance(account.data_len()); + if account.lamports() > minimum_balance { + let value = account.lamports() - minimum_balance; + **account.lamports.borrow_mut() -= value; + + Ok(value) + } else { + Ok(0) + } +} + +fn kill_account(account: &AccountInfo) -> Result { + let value = account.lamports(); + + **account.try_borrow_mut_lamports()? = 0; + account.realloc(0, false)?; + account.assign(&system_program::ID); + + Ok(value) +} + +fn update_ether_account_from_v1( + legacy_data: &LegacyEtherData, + db: &AccountsDB, + keys: &KeysCache, + rent: &Rent, +) -> Result { + let pubkey = keys.contract(&crate::ID, legacy_data.address); + let account = db.get(&pubkey); + + let mut lamports_collected = 0_u64; + + if (legacy_data.generation > 0) || (legacy_data.code_size > 0) { + // This is contract account. Convert it to new format + super::validate_tag(&crate::ID, account, TAG_ACCOUNT_CONTRACT_DEPRECATED)?; + + // Read existing data + let storage = legacy_data.read_storage(account); + let code = legacy_data.read_code(account); + + // Make account smaller + let required_len = ContractAccount::required_account_size(&code); + lamports_collected += reduce_account_size(account, required_len, rent)?; + + // Fill it with new data + account.try_borrow_mut_data()?.fill(0); + + let mut contract = ContractAccount::create( + legacy_data.address, + crate::config::DEFAULT_CHAIN_ID, + legacy_data.generation, + &code, + db, + Some(keys), + )?; + contract.set_storage_multiple_values(0, &storage); + } else { + // This is not contract. Just kill it. + lamports_collected += kill_account(account)?; + } + + if (legacy_data.balance > 0) || (legacy_data.trx_count > 0) { + // Has balance data. Create a new account + let mut balance = BalanceAccount::create( + legacy_data.address, + crate::config::DEFAULT_CHAIN_ID, + db, + Some(keys), + rent, + )?; + balance.mint(legacy_data.balance)?; + balance.increment_nonce_by(legacy_data.trx_count)?; + } + + Ok(lamports_collected) +} + +fn update_storage_account_from_v1( + legacy_data: &LegacyStorageData, + db: &AccountsDB, + keys: &KeysCache, + rent: &Rent, +) -> Result { + let mut lamports_collected = 0_u64; + + let cell_pubkey = keys.storage_cell(&crate::ID, legacy_data.address, legacy_data.index); + let cell_account = db.get(&cell_pubkey).clone(); + + let contract_pubkey = keys.contract(&crate::ID, legacy_data.address); + let contract_account = db.get(&contract_pubkey).clone(); + let contract = ContractAccount::from_account(&crate::ID, contract_account)?; + + if contract.generation() != legacy_data.generation { + // Cell is out of date. Kill it. + lamports_collected += kill_account(&cell_account)?; + return Ok(lamports_collected); + } + + let cells = legacy_data.read_cells(&cell_account); + + // Make account smaller + let required_len = StorageCell::required_account_size(cells.len()); + lamports_collected += reduce_account_size(&cell_account, required_len, rent)?; + + // Fill it with new data + cell_account.try_borrow_mut_data()?.fill(0); + super::set_tag( + &crate::ID, + &cell_account, + TAG_STORAGE_CELL, + StorageCellHeader::VERSION, + )?; + + let mut storage = StorageCell::from_account(&crate::ID, cell_account)?; + storage.cells_mut().copy_from_slice(&cells); + storage.increment_revision(rent, db)?; + + Ok(lamports_collected) +} + +pub fn update_holder_account(account: &AccountInfo) -> Result { + match super::tag(&crate::ID, account)? { + TAG_HOLDER_DEPRECATED => { + let legacy_data = LegacyHolderData::from_account(&crate::ID, account)?; + + super::set_tag(&crate::ID, account, TAG_HOLDER, HolderHeader::VERSION)?; + + let mut holder = Holder::from_account(&crate::ID, account.clone())?; + holder.update(|data| { + data.owner = legacy_data.owner; + data.transaction_hash.fill(0); + data.transaction_len = 0; + }); + + Ok(TAG_HOLDER) + } + TAG_STATE_FINALIZED_DEPRECATED => { + let legacy_data = LegacyFinalizedData::from_account(&crate::ID, account)?; + + super::set_tag( + &crate::ID, + account, + TAG_STATE_FINALIZED, + StateFinalizedHeader::VERSION, + )?; + + let mut state = StateFinalizedAccount::from_account(&crate::ID, account.clone())?; + state.update(|data| { + data.owner = legacy_data.owner; + data.transaction_hash = legacy_data.transaction_hash; + }); + + Ok(TAG_STATE_FINALIZED) + } + tag => Ok(tag), + } +} + +pub fn update_legacy_accounts(accounts: &AccountsDB) -> Result { + let keys = KeysCache::new(); + + let mut lamports_collected = 0_u64; + let mut legacy_storage = Vec::with_capacity(accounts.accounts_len()); + + let rent = Rent::get()?; + + for account in accounts { + if !crate::check_id(account.owner) { + continue; + } + + if account.data_is_empty() { + continue; + } + + let tag = account.try_borrow_data()?[0]; + match tag { + LegacyEtherData::TAG => { + let legacy_data = LegacyEtherData::from_account(&crate::ID, account)?; + lamports_collected += + update_ether_account_from_v1(&legacy_data, accounts, &keys, &rent)?; + } + LegacyStorageData::TAG => { + let legacy_data = LegacyStorageData::from_account(&crate::ID, account)?; + legacy_storage.push(legacy_data); + } + _ => {} + } + } + + for data in legacy_storage { + lamports_collected += update_storage_account_from_v1(&data, accounts, &keys, &rent)?; + } + + Ok(lamports_collected) +} + +#[must_use] +pub fn is_legacy_tag(tag: u8) -> bool { + matches!( + tag, + TAG_ACCOUNT_CONTRACT_DEPRECATED + | TAG_STORAGE_CELL_DEPRECATED + | TAG_HOLDER_DEPRECATED + | TAG_STATE_FINALIZED_DEPRECATED + | TAG_STATE_DEPRECATED + | TAG_STATE_BEFORE_REVISION + ) +} diff --git a/evm_loader/program/src/account/mod.rs b/evm_loader/program/src/account/mod.rs index 317e61002..47b75114e 100644 --- a/evm_loader/program/src/account/mod.rs +++ b/evm_loader/program/src/account/mod.rs @@ -1,263 +1,298 @@ -use std::cell::RefMut; -use std::fmt::Debug; -use std::ops::{Deref, DerefMut}; - use crate::error::{Error, Result}; use solana_program::account_info::AccountInfo; use solana_program::pubkey::Pubkey; use solana_program::rent::Rent; -use solana_program::sysvar::Sysvar; +use std::cell::{Ref, RefMut}; -pub use crate::config::ACCOUNT_SEED_VERSION; +pub use crate::{account_storage::FAKE_OPERATOR, config::ACCOUNT_SEED_VERSION}; +pub use ether_balance::{BalanceAccount, Header as BalanceHeader}; +pub use ether_contract::{AllocateResult, ContractAccount, Header as ContractHeader}; +pub use ether_storage::{Cell, Header as StorageCellHeader, StorageCell, StorageCellAddress}; +pub use holder::{Header as HolderHeader, Holder}; pub use incinerator::Incinerator; pub use operator::Operator; +pub use operator_balance::{OperatorBalanceAccount, OperatorBalanceValidator}; +pub use state::{AccountsStatus, StateAccount}; +pub use state_finalized::{Header as StateFinalizedHeader, StateFinalizedAccount}; pub use treasury::{MainTreasury, Treasury}; -pub mod ether_account; -pub mod ether_contract; -pub mod ether_storage; -pub mod holder; +use self::program::System; + +mod ether_balance; +mod ether_contract; +mod ether_storage; +mod holder; mod incinerator; +pub mod legacy; mod operator; +mod operator_balance; pub mod program; -pub mod state; -pub mod sysvar; +mod state; +mod state_finalized; pub mod token; mod treasury; -/* -Deprecated tags: - -const TAG_ACCOUNT_V1: u8 = 1; -const TAG_ACCOUNT_V2: u8 = 10; -const TAG_CONTRACT_V1: u8 = 2; -const TAG_CONTRACT_V2: u8 = 20; -const TAG_CONTRACT_STORAGE: u8 = 6; -const TAG_STATE_V1: u8 = 3; -const TAG_STATE_V2: u8 = 30; -const TAG_STATE_V3: u8 = 21; -const TAG_ERC20_ALLOWANCE: u8 = 4; -const TAG_FINALIZED_STATE: u8 = 5; -const TAG_HOLDER: u8 = 6; -*/ +pub const HEAP_OFFSET_PTR: usize = holder::HEAP_OFFSET_OFFSET; pub const TAG_EMPTY: u8 = 0; -const TAG_ACCOUNT_V3: u8 = 12; -const TAG_STATE: u8 = 22; -const TAG_FINALIZED_STATE: u8 = 31; -const TAG_CONTRACT_STORAGE: u8 = 42; -const TAG_HOLDER: u8 = 51; - -pub type EthereumAccount<'a> = AccountData<'a, ether_account::Data>; -pub type EthereumStorage<'a> = AccountData<'a, ether_storage::Data>; -pub type State<'a> = AccountData<'a, state::Data>; -pub type FinalizedState<'a> = AccountData<'a, state::FinalizedData>; -pub type Holder<'a> = AccountData<'a, holder::Data>; - -pub trait Packable { - const TAG: u8; - const SIZE: usize; - - fn unpack(data: &[u8]) -> Self; - fn pack(&self, data: &mut [u8]); +pub const TAG_STATE: u8 = 25; +pub const TAG_STATE_FINALIZED: u8 = 32; +pub const TAG_HOLDER: u8 = 52; + +pub const TAG_ACCOUNT_BALANCE: u8 = 60; +pub const TAG_ACCOUNT_CONTRACT: u8 = 70; +pub const TAG_OPERATOR_BALANCE: u8 = 80; +pub const TAG_STORAGE_CELL: u8 = 43; + +const TAG_OFFSET: usize = 0; +const HEADER_VERSION_OFFSET: usize = 1; +pub const ACCOUNT_PREFIX_LEN: usize = 1/*tag*/ + 1/*header version*/; + +#[inline] +fn section<'r, T>(account: &'r AccountInfo<'_>, offset: usize) -> Ref<'r, T> { + let begin = offset; + let end = begin + std::mem::size_of::(); + + let data = account.data.borrow(); + Ref::map(data, |d| { + let bytes = &d[begin..end]; + + assert_eq!(std::mem::align_of::(), 1); + assert_eq!(std::mem::size_of::(), bytes.len()); + unsafe { &*(bytes.as_ptr().cast()) } + }) } -struct AccountParts<'a> { - tag: RefMut<'a, u8>, - data: RefMut<'a, [u8]>, - remaining: RefMut<'a, [u8]>, -} +#[inline] +fn section_mut<'r, T>(account: &'r AccountInfo<'_>, offset: usize) -> RefMut<'r, T> { + let begin = offset; + let end = begin + std::mem::size_of::(); + + let data = account.data.borrow_mut(); + RefMut::map(data, |d| { + let bytes = &mut d[begin..end]; -#[derive(Debug)] -pub struct AccountData<'a, T> -where - T: Packable + Debug, -{ - dirty: bool, - data: T, - pub info: &'a AccountInfo<'a>, + assert_eq!(std::mem::align_of::(), 1); + assert_eq!(std::mem::size_of::(), bytes.len()); + unsafe { &mut *(bytes.as_mut_ptr().cast()) } + }) } -fn split_account_data<'a>(info: &'a AccountInfo<'a>, data_len: usize) -> Result { - if info.data_len() < 1 + data_len { - return Err(Error::AccountInvalidData(*info.key)); - } +trait AccountHeader { + const VERSION: u8; +} +struct NoHeader {} +impl AccountHeader for NoHeader { + const VERSION: u8 = 0; +} - let account_data = info.try_borrow_mut_data()?; - let (tag, bytes) = RefMut::map_split(account_data, |d| { - d.split_first_mut().expect("data is not empty") - }); - let (data, remaining) = RefMut::map_split(bytes, |d| d.split_at_mut(data_len)); +#[inline] +fn header<'r, T: AccountHeader>(account: &'r AccountInfo<'_>) -> Ref<'r, T> { + section(account, ACCOUNT_PREFIX_LEN) +} - Ok(AccountParts { - tag, - data, - remaining, - }) +#[inline] +fn header_mut<'r, T: AccountHeader>(account: &'r AccountInfo<'_>) -> RefMut<'r, T> { + section_mut(account, ACCOUNT_PREFIX_LEN) } -impl<'a, T> AccountData<'a, T> -where - T: Packable + Debug, -{ - pub const SIZE: usize = 1 + T::SIZE; - pub const TAG: u8 = T::TAG; +fn expand_header<'a, From: AccountHeader, To: AccountHeader>( + account: &AccountInfo<'a>, + rent: &Rent, + db: &AccountsDB<'a>, +) -> Result<()> { + let from_len = std::mem::size_of::(); + let to_len = std::mem::size_of::(); - pub fn from_account(program_id: &Pubkey, info: &'a AccountInfo<'a>) -> Result { - if info.owner != program_id { - return Err(Error::AccountInvalidOwner(*info.key, *program_id)); - } + assert!(to_len >= from_len); + assert!(account.data_len() >= ACCOUNT_PREFIX_LEN + from_len); - let parts = split_account_data(info, T::SIZE)?; - if *parts.tag != T::TAG { - return Err(Error::AccountInvalidTag(*info.key, T::TAG)); - } + let data_len = account.data_len() - ACCOUNT_PREFIX_LEN - from_len; + let required_len = ACCOUNT_PREFIX_LEN + to_len + data_len; + assert!(required_len >= account.data_len()); + + account.realloc(required_len, false)?; - let data = T::unpack(&parts.data); + let minimum_balance = rent.minimum_balance(required_len); + if account.lamports() < minimum_balance { + let required_lamports = minimum_balance - account.lamports(); - Ok(Self { - dirty: false, - data, - info, - }) + let system = db.system(); + let operator = db.operator(); + system.transfer(operator, account, required_lamports)?; } - pub fn init(program_id: &Pubkey, info: &'a AccountInfo<'a>, data: T) -> Result { - if info.owner != program_id { - return Err(Error::AccountInvalidOwner(*info.key, *program_id)); - } + { + let mut account_data = account.try_borrow_mut_data()?; + + let begin = ACCOUNT_PREFIX_LEN + from_len; + let end = begin + data_len; + let target = ACCOUNT_PREFIX_LEN + to_len; + account_data.copy_within(begin..end, target); + account_data[begin..target].fill(0); + account_data[HEADER_VERSION_OFFSET] = To::VERSION; + } - if !info.is_writable { - return Err(Error::AccountNotWritable(*info.key)); - } + Ok(()) +} - let rent = Rent::get()?; - if !rent.is_exempt(info.lamports(), info.data_len()) { - return Err(Error::AccountNotRentExempt(*info.key)); - } +fn header_version(info: &AccountInfo) -> u8 { + // This is used only inside the module and account validation should be already done + let data = info.data.borrow(); + data[HEADER_VERSION_OFFSET] +} - let mut parts = split_account_data(info, T::SIZE)?; - if *parts.tag != TAG_EMPTY { - return Err(Error::AccountAlreadyInitialized(*info.key)); - } +pub fn tag(program_id: &Pubkey, info: &AccountInfo) -> Result { + if info.owner != program_id { + return Err(Error::AccountInvalidOwner(*info.key, *program_id)); + } - *parts.tag = T::TAG; - data.pack(&mut parts.data); + let data = info.try_borrow_data()?; + if data.len() < ACCOUNT_PREFIX_LEN { + return Err(Error::AccountInvalidData(*info.key)); + } - parts.remaining.fill(0); + Ok(data[TAG_OFFSET]) +} - Ok(Self { - dirty: false, - data, - info, - }) - } +pub fn set_tag(program_id: &Pubkey, info: &AccountInfo, tag: u8, header_version: u8) -> Result<()> { + assert_eq!(info.owner, program_id); + + let mut data = info.try_borrow_mut_data()?; + assert!(data.len() >= ACCOUNT_PREFIX_LEN); - /// # Safety - /// *Delete account*. Transfer lamports to the operator. - /// All data stored in the account will be lost - pub unsafe fn suicide(mut self, operator: &Operator<'a>) { - let info = self.info; + data[TAG_OFFSET] = tag; + data[HEADER_VERSION_OFFSET] = header_version; + + Ok(()) +} - self.dirty = false; // Do not save data into solana account - core::mem::drop(self); // Release borrowed account data +pub fn validate_tag(program_id: &Pubkey, info: &AccountInfo, tag: u8) -> Result<()> { + let account_tag = crate::account::tag(program_id, info)?; - crate::account::delete(info, operator); + if account_tag == tag { + Ok(()) + } else { + Err(Error::AccountInvalidTag(*info.key, tag)) } +} - /// # Safety - /// Should be used with care. Can corrupt account data - pub unsafe fn replace(mut self, data: U) -> Result> - where - U: Packable + Debug, - { - debug_print!("replace account data from {:?} to {:?}", &self.data, &data); - let info = self.info; +/// # Safety +/// *Permanently delete all data* in the account. Transfer lamports to the operator. +pub unsafe fn delete(account: &AccountInfo, operator: &Operator) { + debug_print!("DELETE ACCOUNT {}", account.key); - if !info.is_writable { - return Err(Error::AccountNotWritable(*info.key)); - } + **operator.lamports.borrow_mut() += account.lamports(); + **account.lamports.borrow_mut() = 0; - self.dirty = false; // Do not save data into solana account - core::mem::drop(self); // Release borrowed account data + let mut data = account.data.borrow_mut(); + data.fill(0); +} + +pub struct AccountsDB<'a> { + sorted_accounts: Vec>, + operator: Operator<'a>, + operator_balance: Option>, + system: Option>, + treasury: Option>, +} - let mut parts = split_account_data(info, U::SIZE)?; +impl<'a> AccountsDB<'a> { + #[must_use] + pub fn new( + accounts: &[AccountInfo<'a>], + operator: Operator<'a>, + operator_balance: Option>, + system: Option>, + treasury: Option>, + ) -> Self { + let mut sorted_accounts = accounts.to_vec(); + sorted_accounts.sort_unstable_by_key(|a| a.key); + sorted_accounts.dedup_by_key(|a| a.key); + + Self { + sorted_accounts, + operator, + operator_balance, + system, + treasury, + } + } - *parts.tag = U::TAG; - data.pack(&mut parts.data); + #[must_use] + pub fn accounts_len(&self) -> usize { + self.sorted_accounts.len() + } - parts.remaining.fill(0); + #[must_use] + pub fn system(&self) -> &System<'a> { + if let Some(system) = &self.system { + return system; + } - Ok(AccountData { - dirty: false, - data, - info, - }) + panic!("System Account must be present in the transaction"); } -} -impl<'a, T> Deref for AccountData<'a, T> -where - T: Packable + Debug, -{ - type Target = T; + #[must_use] + pub fn treasury(&self) -> &Treasury<'a> { + if let Some(treasury) = &self.treasury { + return treasury; + } - fn deref(&self) -> &Self::Target { - &self.data + panic!("Treasury Account must be present in the transaction"); } -} -impl<'a, T> DerefMut for AccountData<'a, T> -where - T: Packable + Debug, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.dirty = true; - &mut self.data + #[must_use] + pub fn operator(&self) -> &Operator<'a> { + &self.operator } -} -impl<'a, T> Drop for AccountData<'a, T> -where - T: Packable + Debug, -{ - fn drop(&mut self) { - if !self.dirty { - return; + #[must_use] + pub fn operator_balance(&self) -> OperatorBalanceAccount<'a> { + if let Some(operator_balance) = &self.operator_balance { + return operator_balance.clone(); } - debug_print!("Save into solana account {:?}", self.data); - assert!(self.info.is_writable); - - let mut parts = - split_account_data(self.info, T::SIZE).expect("Account have incorrect size"); + panic!("Operator Balance Account must be present in the transaction"); + } - self.data.pack(&mut parts.data); + #[must_use] + pub fn try_operator_balance(&self) -> Option> { + self.operator_balance.clone() } -} -pub fn tag(program_id: &Pubkey, info: &AccountInfo) -> Result { - if info.owner != program_id { - return Err(Error::AccountInvalidOwner(*info.key, *program_id)); + #[must_use] + pub fn operator_key(&self) -> Pubkey { + *self.operator.key } - let data = info.try_borrow_data()?; - if data.is_empty() { - return Err(Error::AccountInvalidData(*info.key)); + #[must_use] + pub fn operator_info(&self) -> &AccountInfo<'a> { + &self.operator } - Ok(data[0]) -} + #[must_use] + pub fn get(&self, pubkey: &Pubkey) -> &AccountInfo<'a> { + if pubkey == &FAKE_OPERATOR || pubkey == self.operator.key { + return self.operator_info(); + } + let index = self + .sorted_accounts + .binary_search_by_key(&pubkey, |a| a.key) + .unwrap_or_else(|_| panic!("address {pubkey} must be present in the transaction")); -/// # Safety -/// *Permanently delete all data* in the account. Transfer lamports to the operator. -pub unsafe fn delete(account: &AccountInfo, operator: &Operator) { - debug_print!("DELETE ACCOUNT {}", account.key); + // We just got an 'index' from the binary_search over this vector. + unsafe { self.sorted_accounts.get_unchecked(index) } + } +} - **operator.lamports.borrow_mut() += account.lamports(); - **account.lamports.borrow_mut() = 0; +#[allow(clippy::into_iter_without_iter)] +impl<'a, 'r> IntoIterator for &'r AccountsDB<'a> { + type Item = &'r AccountInfo<'a>; + type IntoIter = std::slice::Iter<'r, AccountInfo<'a>>; - let mut data = account.data.borrow_mut(); - data.fill(0); + fn into_iter(self) -> Self::IntoIter { + self.sorted_accounts.iter() + } } diff --git a/evm_loader/program/src/account/operator.rs b/evm_loader/program/src/account/operator.rs index aa9318b70..82581df26 100644 --- a/evm_loader/program/src/account/operator.rs +++ b/evm_loader/program/src/account/operator.rs @@ -3,6 +3,7 @@ use solana_program::account_info::AccountInfo; use solana_program::program_error::ProgramError; use std::ops::Deref; +#[derive(Clone)] pub struct Operator<'a> { pub info: &'a AccountInfo<'a>, } diff --git a/evm_loader/program/src/account/operator_balance.rs b/evm_loader/program/src/account/operator_balance.rs new file mode 100644 index 000000000..753bd22ae --- /dev/null +++ b/evm_loader/program/src/account/operator_balance.rs @@ -0,0 +1,236 @@ +use std::mem::size_of; + +use crate::{ + error::{Error, Result}, + types::{Address, Transaction}, +}; +use ethnum::U256; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey, rent::Rent, system_program}; + +use super::{ + program, AccountHeader, BalanceAccount, Operator, ACCOUNT_PREFIX_LEN, ACCOUNT_SEED_VERSION, + TAG_OPERATOR_BALANCE, +}; + +#[repr(C, packed)] +pub struct Header { + pub owner: Pubkey, + pub address: Address, + pub chain_id: u64, + pub balance: U256, +} +impl AccountHeader for Header { + const VERSION: u8 = 2; +} + +#[derive(Clone)] +pub struct OperatorBalanceAccount<'a> { + account: &'a AccountInfo<'a>, +} + +impl<'a> OperatorBalanceAccount<'a> { + #[must_use] + pub fn required_account_size() -> usize { + ACCOUNT_PREFIX_LEN + size_of::
() + } + + pub fn from_account(program_id: &Pubkey, account: &'a AccountInfo<'a>) -> Result { + super::validate_tag(program_id, account, TAG_OPERATOR_BALANCE)?; + + Ok(Self { account }) + } + + pub fn try_from_account( + program_id: &Pubkey, + account: &'a AccountInfo<'a>, + ) -> Result> { + if system_program::check_id(account.owner) { + Ok(None) + } else { + let balance = Self::from_account(program_id, account)?; + Ok(Some(balance)) + } + } + + pub fn create( + address: Address, + chain_id: u64, + account: &'a AccountInfo<'a>, + operator: &Operator<'a>, + system: &program::System<'a>, + rent: &Rent, + ) -> Result { + let (pubkey, bump_seed) = address.find_operator_address(&crate::ID, chain_id, operator); + + if account.key != &pubkey { + return Err(Error::AccountInvalidKey(*account.key, pubkey)); + } + + // Already created. Return immidiately + if !system_program::check_id(account.owner) { + let balance_account = Self::from_account(&crate::ID, account)?; + assert_eq!(balance_account.address(), address); + assert_eq!(balance_account.chain_id(), chain_id); + assert_eq!(balance_account.owner(), *operator.key); + + return Ok(balance_account); + } + + // Create a new account + let program_seeds: &[&[u8]] = &[ + &[ACCOUNT_SEED_VERSION], + operator.key.as_ref(), + address.as_bytes(), + &U256::from(chain_id).to_be_bytes(), + &[bump_seed], + ]; + + system.create_pda_account( + &crate::ID, + operator, + account, + program_seeds, + Self::required_account_size(), + rent, + )?; + + super::set_tag(&crate::ID, account, TAG_OPERATOR_BALANCE, Header::VERSION)?; + { + let mut header = super::header_mut::
(account); + header.owner = *operator.key; + header.address = address; + header.chain_id = chain_id; + header.balance = U256::ZERO; + } + + Ok(Self { account }) + } + + #[must_use] + pub fn pubkey(&self) -> &'a Pubkey { + self.account.key + } + + #[must_use] + pub fn address(&self) -> Address { + let header = super::header::
(self.account); + header.address + } + + #[must_use] + pub fn chain_id(&self) -> u64 { + let header = super::header::
(self.account); + header.chain_id + } + + #[must_use] + pub fn balance(&self) -> U256 { + let header = super::header::
(self.account); + header.balance + } + + #[must_use] + pub fn owner(&self) -> Pubkey { + let header = super::header::
(self.account); + header.owner + } + + pub fn validate_owner(&self, operator: &Operator) -> Result<()> { + let owner = self.owner(); + if &owner != operator.key { + return Err(Error::OperatorBalanceInvalidOwner(owner, *operator.key)); + } + + Ok(()) + } + + pub fn consume_gas(&mut self, source: &mut BalanceAccount, value: U256) -> Result<()> { + if self.chain_id() != source.chain_id() { + return Err(Error::OperatorBalanceInvalidChainId); + } + + source.burn(value)?; + self.mint(value) + } + + pub fn withdraw(&mut self, target: &mut BalanceAccount) -> Result<()> { + if self.chain_id() != target.chain_id() { + return Err(Error::OperatorBalanceInvalidChainId); + } + + if self.address() != target.address() { + return Err(Error::OperatorBalanceInvalidAddress); + } + + let value = self.balance(); + + self.burn(value)?; + target.mint(value) + } + + pub fn burn(&mut self, value: U256) -> Result<()> { + let mut header = super::header_mut::
(self.account); + + header.balance = header + .balance + .checked_sub(value) + .ok_or(Error::InsufficientBalance( + header.address, + header.chain_id, + value, + ))?; + + Ok(()) + } + + pub fn mint(&mut self, value: U256) -> Result<()> { + let mut header = super::header_mut::
(self.account); + + header.balance = header + .balance + .checked_add(value) + .ok_or(Error::IntegerOverflow)?; + + Ok(()) + } + + /// # Safety + /// Permanently deletes Operator Balance account and all data in it + pub unsafe fn suicide(self, operator: &Operator) { + assert_eq!(self.balance(), U256::ZERO); + + crate::account::delete(self.account, operator); + } +} + +pub trait OperatorBalanceValidator { + fn validate(&self, operator: &Operator, trx: &Transaction) -> Result<()> { + self.validate_owner(operator)?; + self.validate_transaction(trx) + } + + fn validate_owner(&self, operator: &Operator) -> Result<()>; + fn validate_transaction(&self, trx: &Transaction) -> Result<()>; + + fn miner(&self, origin: Address) -> Address; +} + +impl OperatorBalanceValidator for Option> { + fn validate_owner(&self, operator: &Operator) -> Result<()> { + let Some(balance) = self else { return Ok(()) }; + balance.validate_owner(operator) + } + + fn validate_transaction(&self, trx: &Transaction) -> Result<()> { + if self.is_none() && (trx.gas_price() != U256::ZERO) { + return Err(Error::OperatorBalanceMissing); + } + + Ok(()) + } + + fn miner(&self, origin: Address) -> Address { + self.as_ref() + .map_or(origin, OperatorBalanceAccount::address) + } +} diff --git a/evm_loader/program/src/account/program.rs b/evm_loader/program/src/account/program.rs index 509585630..0acdd48f0 100644 --- a/evm_loader/program/src/account/program.rs +++ b/evm_loader/program/src/account/program.rs @@ -1,48 +1,14 @@ -use super::{EthereumAccount, Operator, ACCOUNT_SEED_VERSION}; +use super::Operator; use solana_program::account_info::AccountInfo; use solana_program::program::{invoke_signed_unchecked, invoke_unchecked}; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; -use solana_program::{ - program::{invoke, invoke_signed}, - rent::Rent, - system_instruction, - sysvar::Sysvar, -}; +use solana_program::{rent::Rent, system_instruction}; use std::convert::From; use std::ops::Deref; -pub struct Neon<'a>(&'a AccountInfo<'a>); - -impl<'a> Neon<'a> { - pub fn from_account( - program_id: &Pubkey, - info: &'a AccountInfo<'a>, - ) -> Result { - if program_id != info.key { - return Err!(ProgramError::InvalidArgument; "Account {} - is not Neon program", info.key); - } - - Ok(Self(info)) - } -} - -impl<'a> Deref for Neon<'a> { - type Target = AccountInfo<'a>; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - pub struct System<'a>(&'a AccountInfo<'a>); -impl<'a> From<&'a AccountInfo<'a>> for System<'a> { - fn from(info: &'a AccountInfo<'a>) -> Self { - Self(info) - } -} - impl<'a> From<&System<'a>> for &'a AccountInfo<'a> { fn from(f: &System<'a>) -> Self { f.0 @@ -60,66 +26,64 @@ impl<'a> System<'a> { pub fn create_pda_account( &self, - program_id: &Pubkey, + owner: &Pubkey, payer: &Operator<'a>, new_account: &AccountInfo<'a>, new_account_seeds: &[&[u8]], space: usize, + rent: &Rent, ) -> Result<(), ProgramError> { - let rent = Rent::get()?; let minimum_balance = rent.minimum_balance(space).max(1); if new_account.lamports() > 0 { let required_lamports = minimum_balance.saturating_sub(new_account.lamports()); if required_lamports > 0 { - invoke( + invoke_unchecked( &system_instruction::transfer(payer.key, new_account.key, required_lamports), - &[(*payer).clone(), new_account.clone(), self.0.clone()], + &[payer.info.clone(), new_account.clone(), self.0.clone()], )?; } - invoke_signed( + invoke_signed_unchecked( &system_instruction::allocate(new_account.key, space as u64), &[new_account.clone(), self.0.clone()], &[new_account_seeds], )?; - invoke_signed( - &system_instruction::assign(new_account.key, program_id), + invoke_signed_unchecked( + &system_instruction::assign(new_account.key, owner), &[new_account.clone(), self.0.clone()], &[new_account_seeds], ) } else { - invoke_signed( + invoke_signed_unchecked( &system_instruction::create_account( payer.key, new_account.key, minimum_balance, space as u64, - program_id, + owner, ), - &[(*payer).clone(), new_account.clone(), self.0.clone()], + &[payer.info.clone(), new_account.clone(), self.0.clone()], &[new_account_seeds], ) } } + #[allow(clippy::too_many_arguments)] pub fn create_account_with_seed( &self, - payer: &Operator<'a>, - base: &EthereumAccount<'a>, owner: &Pubkey, + payer: &Operator<'a>, + base: &AccountInfo<'a>, + signer_seeds: &[&[u8]], new_account: &AccountInfo<'a>, seed: &str, space: usize, + rent: &Rent, ) -> Result<(), ProgramError> { - let minimum_balance = Rent::get()?.minimum_balance(space).max(1); - let signer_seeds: &[&[u8]] = &[ - &[ACCOUNT_SEED_VERSION], - base.address.as_bytes(), - &[base.bump_seed], - ]; + let minimum_balance = rent.minimum_balance(space).max(1); if new_account.lamports() > 0 { let required_lamports = minimum_balance.saturating_sub(new_account.lamports()); @@ -127,25 +91,25 @@ impl<'a> System<'a> { if required_lamports > 0 { invoke_unchecked( &system_instruction::transfer(payer.key, new_account.key, required_lamports), - &[(*payer).clone(), new_account.clone(), self.0.clone()], + &[payer.info.clone(), new_account.clone(), self.0.clone()], )?; } invoke_signed_unchecked( &system_instruction::allocate_with_seed( new_account.key, - base.info.key, + base.key, seed, space as u64, owner, ), - &[new_account.clone(), base.info.clone(), self.0.clone()], + &[new_account.clone(), base.clone(), self.0.clone()], &[signer_seeds], )?; invoke_signed_unchecked( - &system_instruction::assign_with_seed(new_account.key, base.info.key, seed, owner), - &[new_account.clone(), base.info.clone(), self.0.clone()], + &system_instruction::assign_with_seed(new_account.key, base.key, seed, owner), + &[new_account.clone(), base.clone(), self.0.clone()], &[signer_seeds], ) } else { @@ -153,16 +117,16 @@ impl<'a> System<'a> { &system_instruction::create_account_with_seed( payer.key, new_account.key, - base.info.key, + base.key, seed, minimum_balance, space as u64, owner, ), &[ - (*payer).clone(), + payer.info.clone(), new_account.clone(), - base.info.clone(), + base.clone(), self.0.clone(), ], &[signer_seeds], @@ -183,9 +147,9 @@ impl<'a> System<'a> { target.key ); - invoke( + invoke_unchecked( &system_instruction::transfer(source.key, target.key, lamports), - &[(*source).clone(), target.clone(), self.0.clone()], + &[source.info.clone(), target.clone(), self.0.clone()], ) } } @@ -215,7 +179,7 @@ impl<'a> Token<'a> { mint: &AccountInfo<'a>, owner: &AccountInfo<'a>, ) -> Result<(), ProgramError> { - invoke( + invoke_unchecked( &spl_token::instruction::initialize_account3( self.0.key, account.key, diff --git a/evm_loader/program/src/account/state.rs b/evm_loader/program/src/account/state.rs index 8a02559f7..a0b104bc5 100644 --- a/evm_loader/program/src/account/state.rs +++ b/evm_loader/program/src/account/state.rs @@ -1,139 +1,613 @@ -use super::Packable; -use crate::types::Address; -use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; +use std::cell::{Ref, RefMut}; +use std::mem::{align_of, size_of, ManuallyDrop}; +use std::ptr::{addr_of, read_unaligned}; + +use crate::account_storage::AccountStorage; +use crate::allocator::acc_allocator; +use crate::config::DEFAULT_CHAIN_ID; +use crate::debug::log_data; +use crate::error::{Error, Result}; +use crate::evm::database::Database; +use crate::evm::tracing::EventListener; +use crate::evm::Machine; +use crate::executor::ExecutorStateData; +use crate::types::boxx::{boxx, Boxx}; +use crate::types::DynamicFeeTx; +use crate::types::{ + read_raw_utils::{read_vec, ReconstructRaw}, + AccessListTx, Address, LegacyTx, Transaction, TransactionPayload, TreeMap, +}; + use ethnum::U256; -use solana_program::pubkey::Pubkey; +use solana_program::hash::Hash; +use solana_program::system_program; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use super::{ + AccountHeader, AccountsDB, BalanceAccount, ContractAccount, Holder, OperatorBalanceAccount, + StateFinalizedAccount, StorageCell, ACCOUNT_PREFIX_LEN, TAG_ACCOUNT_BALANCE, + TAG_ACCOUNT_CONTRACT, TAG_HOLDER, TAG_STATE, TAG_STATE_FINALIZED, TAG_STORAGE_CELL, +}; + +#[derive(PartialEq, Eq)] +pub enum AccountsStatus { + Ok, + NeedRestart, +} + +#[derive(Clone, PartialEq, Eq, Copy)] +#[repr(C)] +enum AccountRevision { + Revision(u32), + Hash([u8; 32]), +} + +impl Default for AccountRevision { + fn default() -> Self { + AccountRevision::Revision(0) + } +} + +impl AccountRevision { + pub fn new(program_id: &Pubkey, info: &AccountInfo) -> Self { + if (info.owner != program_id) && !system_program::check_id(info.owner) { + if crate::config::NO_UPDATE_TRACKING_OWNERS + .binary_search(info.owner) + .is_ok() + { + return AccountRevision::Hash(Hash::default().to_bytes()); + } + + let hash = solana_program::hash::hashv(&[ + info.owner.as_ref(), + &info.lamports().to_le_bytes(), + &info.data.borrow(), + ]); + + return AccountRevision::Hash(hash.to_bytes()); + } + + match crate::account::tag(program_id, info) { + Ok(TAG_STORAGE_CELL) => { + let cell = StorageCell::from_account(program_id, info.clone()).unwrap(); + Self::Revision(cell.revision()) + } + Ok(TAG_ACCOUNT_CONTRACT) => { + let contract = ContractAccount::from_account(program_id, info.clone()).unwrap(); + Self::Revision(contract.revision()) + } + Ok(TAG_ACCOUNT_BALANCE) => { + let balance = BalanceAccount::from_account(program_id, info.clone()).unwrap(); + Self::Revision(balance.revision()) + } + _ => Self::Revision(0), + } + } +} /// Storage data account to store execution metainfo between steps for iterative execution -#[derive(Debug)] -pub struct Data { +#[repr(C)] +struct Data { pub owner: Pubkey, - pub transaction_hash: [u8; 32], + pub transaction: Transaction, /// Ethereum transaction caller address - pub caller: Address, - /// Ethereum transaction gas limit - pub gas_limit: U256, - /// Ethereum transaction gas price - pub gas_price: U256, + pub origin: Address, + /// Stored revision + pub revisions: TreeMap, + /// Accounts that been read during the transaction + pub touched_accounts: TreeMap, /// Ethereum transaction gas used and paid pub gas_used: U256, - /// Operator public key - pub operator: Pubkey, - /// Starting slot for this operator - pub slot: u64, - /// Stored accounts length - pub accounts_len: usize, - /// Stored EVM State length - pub evm_state_len: usize, - /// Stored EVM Machine length - pub evm_machine_len: usize, + /// Ethereum transaction priority fee used and paid in tokens + pub priority_fee_used: U256, + /// Steps executed in the transaction + pub steps_executed: u64, } -/// Storage account data for the finalized transaction state -#[derive(Debug)] -pub struct FinalizedData { - pub owner: Pubkey, - pub transaction_hash: [u8; 32], +// Stores relative offsets for the corresponding objects as allocated by the AccountAllocator. +#[allow(clippy::struct_field_names)] +#[repr(C, packed)] +pub struct Header { + pub executor_state_offset: usize, + pub evm_offset: usize, + pub data_offset: usize, +} +impl AccountHeader for Header { + const VERSION: u8 = 1; +} + +pub struct StateAccount<'a> { + account: AccountInfo<'a>, + // ManuallyDrop to ensure Data is not dropped when StateAccount + // is being dropped (between iterations). + data: ManuallyDrop>, } -impl Packable for Data { - /// Storage struct tag - const TAG: u8 = super::TAG_STATE; - /// Storage struct serialized size - const SIZE: usize = 32 + 32 + 20 + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8; +type StateAccountCoreApiView = (Transaction, Pubkey, Address, Vec, u64); - /// Deserialize `Storage` struct from input data +const BUFFER_OFFSET: usize = ACCOUNT_PREFIX_LEN + size_of::
(); + +impl<'a> StateAccount<'a> { #[must_use] - fn unpack(src: &[u8]) -> Self { - #[allow(clippy::use_self)] - let data = array_ref![src, 0, Data::SIZE]; - let ( + pub fn into_account(self) -> AccountInfo<'a> { + self.account + } + + pub fn from_account(program_id: &Pubkey, account: &AccountInfo<'a>) -> Result { + super::validate_tag(program_id, account, TAG_STATE)?; + + let header = super::header::
(account); + let data_ptr = unsafe { + // Data is more-strictly aligned, but it's safe because we previously initiated it at the exact address. + #[allow(clippy::cast_ptr_alignment)] + account + .data + .borrow() + .as_ptr() + .add(header.data_offset) + .cast::() + .cast_mut() + }; + + Ok(Self { + account: account.clone(), + data: ManuallyDrop::new(unsafe { Boxx::from_raw_in(data_ptr, acc_allocator()) }), + }) + } + + pub fn new( + program_id: &Pubkey, + info: AccountInfo<'a>, + accounts: &AccountsDB<'a>, + origin: Address, + transaction: Transaction, + ) -> Result { + let owner = match super::tag(program_id, &info)? { + TAG_HOLDER => { + let holder = Holder::from_account(program_id, info.clone())?; + holder.validate_owner(accounts.operator())?; + holder.owner() + } + TAG_STATE_FINALIZED => { + let finalized = StateFinalizedAccount::from_account(program_id, info.clone())?; + finalized.validate_owner(accounts.operator())?; + finalized.validate_trx(&transaction)?; + finalized.owner() + } + tag => return Err(Error::AccountInvalidTag(*info.key, tag)), + }; + + // accounts.into_iter returns sorted accounts, so it's safe. + let revisions = accounts + .into_iter() + .map(|account| { + let revision = AccountRevision::new(program_id, account); + (*account.key, revision) + }) + .collect::>(); + + let data = boxx(Data { owner, - hash, - caller, - gas_limit, - gas_price, - gas_used, - operator, - slot, - accounts_len, - evm_state_len, - evm_machine_len, - ) = array_refs![data, 32, 32, 20, 32, 32, 32, 32, 8, 8, 8, 8]; - - Self { - owner: Pubkey::new_from_array(*owner), - transaction_hash: *hash, - caller: Address::from(*caller), - gas_limit: U256::from_le_bytes(*gas_limit), - gas_price: U256::from_le_bytes(*gas_price), - gas_used: U256::from_le_bytes(*gas_used), - operator: Pubkey::new_from_array(*operator), - slot: u64::from_le_bytes(*slot), - accounts_len: usize::from_le_bytes(*accounts_len), - evm_state_len: usize::from_le_bytes(*evm_state_len), - evm_machine_len: usize::from_le_bytes(*evm_machine_len), + transaction, + origin, + revisions, + touched_accounts: TreeMap::new(), + gas_used: U256::ZERO, + priority_fee_used: U256::ZERO, + steps_executed: 0_u64, + }); + + let data_offset = { + let account_data_ptr = info.data.borrow().as_ptr(); + let data_obj_addr = addr_of!(*data).cast::(); + let data_offset = unsafe { data_obj_addr.offset_from(account_data_ptr) }; + #[allow(clippy::cast_sign_loss)] + let data_offset = data_offset as usize; + data_offset + }; + + super::set_tag(program_id, &info, TAG_STATE, Header::VERSION)?; + { + // Set header + let mut header = super::header_mut::
(&info); + header.executor_state_offset = 0; + header.evm_offset = 0; + header.data_offset = data_offset; } + + Ok(Self { + account: info, + data: ManuallyDrop::new(data), + }) } - /// Serialize `Storage` struct into given destination - fn pack(&self, dst: &mut [u8]) { - #[allow(clippy::use_self)] - let data = array_mut_ref![dst, 0, Data::SIZE]; - let ( - owner, - hash, - caller, - gas_limit, - gas_price, - gas_used, - operator, - slot, - accounts_len, - evm_state_len, - evm_machine_len, - ) = mut_array_refs![data, 32, 32, 20, 32, 32, 32, 32, 8, 8, 8, 8]; - - owner.copy_from_slice(self.owner.as_ref()); - hash.copy_from_slice(&self.transaction_hash); - *caller = self.caller.into(); - *gas_limit = self.gas_limit.to_le_bytes(); - *gas_price = self.gas_price.to_le_bytes(); - *gas_used = self.gas_used.to_le_bytes(); - operator.copy_from_slice(self.operator.as_ref()); - *slot = self.slot.to_le_bytes(); - *accounts_len = self.accounts_len.to_le_bytes(); - *evm_state_len = self.evm_state_len.to_le_bytes(); - *evm_machine_len = self.evm_machine_len.to_le_bytes(); + pub fn restore( + program_id: &Pubkey, + info: &AccountInfo<'a>, + accounts: &AccountsDB, + ) -> Result<(Self, AccountsStatus)> { + let mut status = AccountsStatus::Ok; + let mut state = Self::from_account(program_id, info)?; + + let is_touched_account = |key: &Pubkey| -> bool { + state + .data + .touched_accounts + .get(key) + .map(|counter| counter > &1) + .is_some() + }; + + let touched_accounts = accounts.into_iter().filter(|a| is_touched_account(a.key)); + for account in touched_accounts { + let account_revision = AccountRevision::new(program_id, account); + let revision_entry = &state.data.revisions[*account.key]; + + if revision_entry != &account_revision { + log_data(&[b"INVALID_REVISION", account.key.as_ref()]); + status = AccountsStatus::NeedRestart; + break; + } + } + + if status == AccountsStatus::NeedRestart { + // update all accounts revisions + for account in accounts { + let account_revision = AccountRevision::new(program_id, account); + state.data.revisions.insert(*account.key, account_revision); + } + } + + Ok((state, status)) + } + + pub fn finalize(self, program_id: &Pubkey) -> Result<()> { + debug_print!("Finalize Storage {}", self.account.key); + + // Change tag to finalized + StateFinalizedAccount::convert_from_state(program_id, self)?; + + Ok(()) + } + + pub fn update_touched_accounts(&mut self, touched: &TreeMap) -> Result<()> { + for (key, counter) in touched.iter() { + self.data + .touched_accounts + .update_or_insert(*key, counter, |v| { + v.checked_add(*counter).ok_or(Error::IntegerOverflow) + })?; + } + + Ok(()) + } + + pub fn accounts(&self) -> impl Iterator { + self.data.revisions.keys() + } + + #[must_use] + pub fn buffer(&self) -> Ref<[u8]> { + let data = self.account.try_borrow_data().unwrap(); + Ref::map(data, |d| &d[BUFFER_OFFSET..]) + } + + #[must_use] + pub fn buffer_mut(&mut self) -> RefMut<[u8]> { + let data = self.account.data.borrow_mut(); + RefMut::map(data, |d| &mut d[BUFFER_OFFSET..]) + } + + #[must_use] + pub fn owner(&self) -> Pubkey { + self.data.owner + } + + #[must_use] + pub fn trx(&self) -> &Transaction { + &self.data.transaction + } + + #[must_use] + pub fn trx_origin(&self) -> Address { + self.data.origin + } + + #[must_use] + pub fn trx_chain_id(&self, backend: &impl AccountStorage) -> u64 { + self.data + .transaction + .chain_id() + .unwrap_or_else(|| backend.default_chain_id()) + } + + #[must_use] + pub fn gas_used(&self) -> U256 { + self.data.gas_used + } + + #[must_use] + fn gas_available(&self) -> U256 { + self.trx().gas_limit().saturating_sub(self.gas_used()) + } + + fn priority_fee_in_tokens_available(&self) -> Result { + Ok(self + .trx() + .priority_fee_limit_in_tokens()? + .saturating_sub(self.data.priority_fee_used)) + } + + fn use_gas(&mut self, amount: U256) -> Result { + if amount == U256::ZERO { + return Ok(U256::ZERO); + } + + let total_gas_used = self.data.gas_used.saturating_add(amount); + let gas_limit = self.trx().gas_limit(); + + if total_gas_used > gas_limit { + return Err(Error::OutOfGas(gas_limit, total_gas_used)); + } + + self.data.gas_used = total_gas_used; + + amount + .checked_mul(self.trx().gas_price()) + .ok_or(Error::IntegerOverflow) + } + + fn use_priority_fee_tokens(&mut self, tokens: U256) -> Result<()> { + let total_priority_fee_used = self.data.priority_fee_used.saturating_add(tokens); + let priority_fee_limit = self.trx().priority_fee_limit_in_tokens()?; + + if total_priority_fee_used > priority_fee_limit { + return Err(Error::OutOfPriorityFee( + priority_fee_limit, + total_priority_fee_used, + )); + } + + self.data.priority_fee_used = total_priority_fee_used; + Ok(()) + } + + pub fn consume_gas( + &mut self, + amount: U256, + priority_fee_tokens: U256, + receiver: Option, + ) -> Result<()> { + self.use_priority_fee_tokens(priority_fee_tokens)?; + let gas_fee_tokens = self.use_gas(amount)?; + + let tokens = gas_fee_tokens + priority_fee_tokens; + if tokens == U256::ZERO { + return Ok(()); + } + + let mut operator_balance = receiver.ok_or(Error::OperatorBalanceMissing)?; + + let trx_chain_id = self.trx().chain_id().unwrap_or(DEFAULT_CHAIN_ID); + if operator_balance.chain_id() != trx_chain_id { + return Err(Error::OperatorBalanceInvalidChainId); + } + + operator_balance.mint(tokens) + } + + pub fn refund_unused_gas(&mut self, origin: &mut BalanceAccount) -> Result<()> { + let trx_chain_id = self.trx().chain_id().unwrap_or(DEFAULT_CHAIN_ID); + + assert!(origin.chain_id() == trx_chain_id); + assert!(origin.address() == self.trx_origin()); + + let unused_gas = self.gas_available(); + let gas_fee_tokens = self.use_gas(unused_gas)?; + + let unused_priority_fee = self.priority_fee_in_tokens_available()?; + self.use_priority_fee_tokens(unused_priority_fee)?; + + origin.mint(gas_fee_tokens + unused_priority_fee) + } + + #[must_use] + pub fn steps_executed(&self) -> u64 { + self.data.steps_executed + } + + pub fn reset_steps_executed(&mut self) { + self.data.steps_executed = 0; + } + + pub fn increment_steps_executed(&mut self, steps: u64) -> Result<()> { + self.data.steps_executed = self + .data + .steps_executed + .checked_add(steps) + .ok_or(Error::IntegerOverflow)?; + + Ok(()) } } -impl Packable for FinalizedData { - /// Finalized storage struct tag - const TAG: u8 = super::TAG_FINALIZED_STATE; - /// Finalized storage struct serialized size - const SIZE: usize = 32 + 32; +// Implementation of functional to save/restore persistent state of iterative transactions. +impl<'a> StateAccount<'a> { + pub fn alloc_executor_state(&self, data: Boxx) { + let offset = self.leak_and_offset(data); + let mut header = super::header_mut::
(&self.account); + header.executor_state_offset = offset; + } + + pub fn dealloc_executor_state(&self) { + unsafe { ManuallyDrop::drop(&mut self.read_executor_state()) }; + let mut header = super::header_mut::
(&self.account); + header.executor_state_offset = 0; + } + + #[must_use] + pub fn read_executor_state(&self) -> ManuallyDrop> { + let header = super::header::
(&self.account); + self.map_obj(header.executor_state_offset) + } - /// Deserialize `FinalizedState` struct from input data #[must_use] - fn unpack(src: &[u8]) -> Self { - #[allow(clippy::use_self)] - let data = array_ref![src, 0, FinalizedData::SIZE]; - let (owner, hash) = array_refs![data, 32, 32]; - - Self { - owner: Pubkey::new_from_array(*owner), - transaction_hash: *hash, + pub fn is_executor_state_alloced(&self) -> bool { + super::header_mut::
(&self.account).executor_state_offset != 0 + } + + pub fn alloc_evm(&self, evm: Boxx>) { + let offset = self.leak_and_offset(evm); + let mut header = super::header_mut::
(&self.account); + header.evm_offset = offset; + } + + pub fn dealloc_evm(&self) { + unsafe { ManuallyDrop::drop(&mut self.read_evm::()) }; + let mut header = super::header_mut::
(&self.account); + header.evm_offset = 0; + } + + #[must_use] + pub fn read_evm(&self) -> ManuallyDrop>> { + let header = super::header::
(&self.account); + self.map_obj(header.evm_offset) + } + + #[must_use] + pub fn is_evm_alloced(&self) -> bool { + super::header_mut::
(&self.account).evm_offset != 0 + } + + /// Leak the Box's underlying data and returns offset from the account data start. + fn leak_and_offset(&self, object: Boxx) -> usize { + let data_ptr = self.account.data.borrow().as_ptr(); + unsafe { + // allocator_api2 does not expose Box::leak (private associated fn). + // We avoid drop of persistent object by leaking via Box::into_raw. + let obj_addr = Boxx::into_raw(object).cast_const().cast::(); + + let offset = obj_addr.offset_from(data_ptr); + assert!(offset > 0); + #[allow(clippy::cast_sign_loss)] + let offset = offset as usize; + offset } } - /// Serialize `FinalizedState` struct into given destination - fn pack(&self, dst: &mut [u8]) { - #[allow(clippy::use_self)] - let data = array_mut_ref![dst, 0, FinalizedData::SIZE]; - let (owner, hash) = mut_array_refs![data, 32, 32]; + fn map_obj(&self, offset: usize) -> ManuallyDrop> { + assert!(offset > 0); + let data = self.account.data.borrow().as_ptr(); + unsafe { + let ptr = data.add(offset).cast_mut(); + assert_eq!(ptr.align_offset(align_of::()), 0); + let data_ptr = ptr.cast::(); + + ManuallyDrop::new(Boxx::from_raw_in(data_ptr, acc_allocator())) + } + } +} + +impl<'a> StateAccount<'a> { + /// Implementation to squeeze bits of information from the state account. + /// N.B. + /// 1. `StateAccount` contains objects and pointers allocated by the state account allocator, so reading + /// objects inside requires jumping on the offset (between the real account address as allocated by the + /// current allocator) and "intended" address of the first account as provided by the Solana runtime. + /// 2. `addr_of!` and `read_unaligned` is heavily used to facilitate the reading of fields by raw pointers. + /// 3. There are upcasts from *const u8 to *const T, but since T was allocated by the allocator previously, + /// it has the correct alignment and the upcast is sound. + #[allow(clippy::cast_ptr_alignment)] + pub fn get_state_account_view( + program_id: &Pubkey, + account: &AccountInfo<'a>, + ) -> Result { + super::validate_tag(program_id, account, TAG_STATE)?; + + let header = super::header::
(account); + let memory_space_delta = { + account.data.borrow().as_ptr() as isize + - isize::try_from(crate::allocator::STATE_ACCOUNT_DATA_ADDRESS)? + }; + // Pointer to the Data is needed to get pointers to the fields in a safe way (using addr_of!). + let data_ptr = unsafe { + account + .data + .borrow() + .as_ptr() + .add(header.data_offset) + .cast::() + .cast_mut() + }; + + unsafe { + // Reading full `Transaction`. + let transaction_ptr = addr_of!((*data_ptr).transaction); + // Memory layout for transaction payload is: tag of enum's variant (u8) followed by the variant value. + // Payload that follows enum tag can have offset due to alignment. + let tx_payload_enum_tag = addr_of!((*transaction_ptr).transaction).cast::(); + let payload_ptr = tx_payload_enum_tag.add(1); - owner.copy_from_slice(self.owner.as_ref()); - hash.copy_from_slice(&self.transaction_hash); + let tx_payload = match read_unaligned(tx_payload_enum_tag) { + 0 => { + let legacy_payload_ptr = + payload_ptr.wrapping_add(payload_ptr.align_offset(align_of::())); + + TransactionPayload::Legacy(LegacyTx::build( + legacy_payload_ptr.cast::(), + memory_space_delta, + )) + } + 1 => { + let access_list_payload_ptr = payload_ptr + .wrapping_add(payload_ptr.align_offset(align_of::())); + + TransactionPayload::AccessList(AccessListTx::build( + access_list_payload_ptr.cast::(), + memory_space_delta, + )) + } + 2 => { + let dynamic_fee_payload_ptr = payload_ptr + .wrapping_add(payload_ptr.align_offset(align_of::())); + + TransactionPayload::DynamicFee(DynamicFeeTx::build( + dynamic_fee_payload_ptr.cast::(), + memory_space_delta, + )) + } + _ => { + return Err(Error::Custom( + "Incorrect transaction payload type.".to_owned(), + )); + } + }; + + let byte_len = read_unaligned(addr_of!((*transaction_ptr).byte_len)); + let hash = read_unaligned(addr_of!((*transaction_ptr).hash)); + let signed_hash = read_unaligned(addr_of!((*transaction_ptr).signed_hash)); + let tx = Transaction { + transaction: tx_payload, + byte_len, + hash, + signed_hash, + }; + + // Reading parts of `StateAccount`. + let owner = read_unaligned(addr_of!((*data_ptr).owner)); + let origin = read_unaligned(addr_of!((*data_ptr).origin)); + let keys_ptr = addr_of!((*data_ptr).revisions).cast::(); + + // Hereby we read the TreeMap and rely on the fact that under the hood it's a Vector<(Pubkey, AccountRevision)>. + // In case the structure changes, it also requires adjustments. + let accounts = read_vec::<(Pubkey, AccountRevision)>(keys_ptr, memory_space_delta) + .iter() + .map(|(key, _)| *key) + .collect(); + + let steps = read_unaligned(addr_of!((*data_ptr).steps_executed)); + + Ok((tx, owner, origin, accounts, steps)) + } } } diff --git a/evm_loader/program/src/account/state_finalized.rs b/evm_loader/program/src/account/state_finalized.rs new file mode 100644 index 000000000..1347817f6 --- /dev/null +++ b/evm_loader/program/src/account/state_finalized.rs @@ -0,0 +1,95 @@ +use std::cell::{Ref, RefMut}; + +use super::{AccountHeader, Operator, StateAccount, TAG_STATE_FINALIZED}; +use crate::{ + error::{Error, Result}, + types::Transaction, +}; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +/// Storage data account to store execution metainfo between steps for iterative execution +#[repr(C, packed)] +pub struct Header { + pub owner: Pubkey, + pub transaction_hash: [u8; 32], +} + +impl AccountHeader for Header { + const VERSION: u8 = 0; +} + +pub struct StateFinalizedAccount<'a> { + account: AccountInfo<'a>, +} + +impl<'a> StateFinalizedAccount<'a> { + pub fn convert_from_state<'s>( + program_id: &Pubkey, + state: StateAccount<'s>, + ) -> Result> { + let owner = state.owner(); + let transaction_hash = state.trx().hash(); + + let account = state.into_account(); + + super::set_tag(program_id, &account, TAG_STATE_FINALIZED, Header::VERSION)?; + { + let mut header = super::header_mut::
(&account); + header.owner = owner; + header.transaction_hash = transaction_hash; + } + + Ok(account) + } + + pub fn from_account(program_id: &Pubkey, account: AccountInfo<'a>) -> Result { + super::validate_tag(program_id, &account, TAG_STATE_FINALIZED)?; + Ok(Self { account }) + } + + #[inline] + #[must_use] + fn header(&self) -> Ref
{ + super::header(&self.account) + } + + #[inline] + #[must_use] + fn header_mut(&mut self) -> RefMut
{ + super::header_mut(&self.account) + } + + pub fn update(&mut self, f: F) + where + F: FnOnce(&mut Header), + { + let mut header = self.header_mut(); + f(&mut header); + } + + #[must_use] + pub fn owner(&self) -> Pubkey { + self.header().owner + } + + #[must_use] + pub fn trx_hash(&self) -> [u8; 32] { + self.header().transaction_hash + } + + pub fn validate_owner(&self, operator: &Operator) -> Result<()> { + if &self.owner() != operator.key { + return Err(Error::HolderInvalidOwner(self.owner(), *operator.key)); + } + + Ok(()) + } + + pub fn validate_trx(&self, transaction: &Transaction) -> Result<()> { + if self.trx_hash() == transaction.hash { + return Err(Error::StorageAccountFinalized); + } + + Ok(()) + } +} diff --git a/evm_loader/program/src/account/sysvar.rs b/evm_loader/program/src/account/sysvar.rs deleted file mode 100644 index 414ba48f9..000000000 --- a/evm_loader/program/src/account/sysvar.rs +++ /dev/null @@ -1,16 +0,0 @@ -use solana_program::account_info::AccountInfo; -use solana_program::program_error::ProgramError; - -pub struct Instructions<'a> { - pub info: &'a AccountInfo<'a>, -} - -impl<'a> Instructions<'a> { - pub fn from_account(info: &'a AccountInfo<'a>) -> Result { - if !solana_program::sysvar::instructions::check_id(info.key) { - return Err!(ProgramError::InvalidArgument; "Account {} - is not sysvar instructions", info.key); - } - - Ok(Self { info }) - } -} diff --git a/evm_loader/program/src/account/token.rs b/evm_loader/program/src/account/token.rs index 9ba6ec152..dc7553de0 100644 --- a/evm_loader/program/src/account/token.rs +++ b/evm_loader/program/src/account/token.rs @@ -19,6 +19,10 @@ impl<'a, T: Pack + IsInitialized> Account<'a, T> { Ok(Self { info, data }) } + + pub fn into_data(self) -> T { + self.data + } } impl<'a, T: Pack + IsInitialized> Deref for Account<'a, T> { diff --git a/evm_loader/program/src/account/treasury.rs b/evm_loader/program/src/account/treasury.rs index fcecc5354..9f090e7bc 100644 --- a/evm_loader/program/src/account/treasury.rs +++ b/evm_loader/program/src/account/treasury.rs @@ -1,7 +1,6 @@ use crate::config::TREASURY_POOL_SEED; -use solana_program::{ - account_info::AccountInfo, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, -}; +use crate::error::{Error, Result}; +use solana_program::{account_info::AccountInfo, program_pack::Pack, pubkey::Pubkey}; use std::ops::Deref; pub struct Treasury<'a> { @@ -19,10 +18,10 @@ impl<'a> Treasury<'a> { program_id: &Pubkey, index: u32, info: &'a AccountInfo<'a>, - ) -> Result { + ) -> Result { let (expected_key, bump_seed) = Treasury::address(program_id, index); if *info.key != expected_key { - return Err!(ProgramError::InvalidArgument; "Account {} - invalid treasure account", info.key); + return Err(Error::AccountInvalidKey(*info.key, expected_key)); } Ok(Self { info, bump_seed }) @@ -51,22 +50,22 @@ impl<'a> Deref for Treasury<'a> { } impl<'a> MainTreasury<'a> { - pub fn from_account( - program_id: &Pubkey, - info: &'a AccountInfo<'a>, - ) -> Result { + pub fn from_account(program_id: &Pubkey, info: &'a AccountInfo<'a>) -> Result { let (expected_key, bump_seed) = MainTreasury::address(program_id); if *info.key != expected_key { - return Err!(ProgramError::InvalidArgument; "Account {} - invalid main treasure account", info.key); + return Err(Error::AccountInvalidKey(*info.key, expected_key)); } if *info.owner != spl_token::id() { - return Err!(ProgramError::InvalidArgument; "Account {} - invalid owner", info.key); + return Err(Error::AccountInvalidOwner(*info.key, spl_token::id())); } let account = spl_token::state::Account::unpack(&info.data.borrow())?; if account.mint != spl_token::native_mint::id() { - return Err!(ProgramError::InvalidArgument; "Account {} - not wrapped SOL spl_token account", info.key); + return Err(Error::Custom(format!( + "Account {} - not wrapped SOL spl_token account", + info.key + ))); } Ok(Self { info, bump_seed }) diff --git a/evm_loader/program/src/account_storage/apply.rs b/evm_loader/program/src/account_storage/apply.rs index ba74df13b..c0d2acfa8 100644 --- a/evm_loader/program/src/account_storage/apply.rs +++ b/evm_loader/program/src/account_storage/apply.rs @@ -1,110 +1,102 @@ -use std::collections::hash_map::Entry; use std::collections::HashMap; -use std::convert::TryInto; use ethnum::U256; -use solana_program::entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE}; use solana_program::instruction::Instruction; -use solana_program::program::{invoke, invoke_signed_unchecked}; -use solana_program::program_error::ProgramError; -use solana_program::rent::Rent; -use solana_program::system_instruction; -use solana_program::sysvar::Sysvar; - -use crate::account::ether_storage::EthereumStorageAddress; -use crate::account::{ether_account, program, EthereumAccount, EthereumStorage, Operator}; -use crate::account_storage::{ - AccountOperation, AccountStorage, AccountsOperations, AccountsReadiness, ProgramAccountStorage, +use solana_program::program::{invoke_signed_unchecked, invoke_unchecked}; +use solana_program::system_program; + +use crate::account::{AllocateResult, BalanceAccount, ContractAccount, StorageCell}; +use crate::account_storage::{ProgramAccountStorage, FAKE_OPERATOR}; +use crate::config::{ + ACCOUNT_SEED_VERSION, PAYMENT_TO_TREASURE, STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT, }; -use crate::config::STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT; +use crate::error::Result; use crate::executor::Action; -use crate::types::Address; +use crate::types::{Address, Vector}; impl<'a> ProgramAccountStorage<'a> { + pub fn transfer_treasury_payment(&mut self) -> Result<()> { + let system = self.accounts.system(); + let treasury = self.accounts.treasury(); + let operator = self.accounts.operator(); + + system.transfer(operator, treasury, PAYMENT_TO_TREASURE)?; + + Ok(()) + } + pub fn transfer_gas_payment( &mut self, origin: Address, - mut operator: EthereumAccount<'a>, + chain_id: u64, value: U256, - ) -> ProgramResult { - let origin_balance = self.balance(&origin); - if origin_balance < value { - return Err!(ProgramError::InsufficientFunds; "Account {} - insufficient funds", origin); - } - - if operator.address == origin { + ) -> Result<()> { + if value == U256::ZERO { return Ok(()); } - if self.ethereum_accounts.contains_key(&operator.address) { - self.transfer_neon_tokens(&origin, &operator.address, value)?; - core::mem::drop(operator); - } else { - let origin_account = self.ethereum_account_mut(&origin); - // balance checked above + let (pubkey, _) = origin.find_balance_address(&crate::ID, chain_id); - origin_account.balance -= value; - operator.balance += value; - } + let source = self.accounts.get(&pubkey).clone(); + let mut source = BalanceAccount::from_account(&crate::ID, source)?; - Ok(()) + let mut target = self.accounts.operator_balance(); + source.increment_revision(&self.rent, &self.accounts)?; + target.consume_gas(&mut source, value) } - pub fn apply_state_change( - &mut self, - neon_program: &program::Neon<'a>, - system_program: &program::System<'a>, - operator: &Operator<'a>, - actions: Vec, - ) -> Result { - debug_print!("Applies begin"); - - let actions = Self::rearrange_actions(actions); - - let accounts_operations = self.calc_accounts_operations(&actions); - if self.process_accounts_operations( - system_program, - neon_program, - operator, - accounts_operations, - )? == AccountsReadiness::NeedMoreReallocations - { - debug_print!( - "Applies postponed: need to reallocate accounts in the next transaction(s)" - ); - return Ok(AccountsReadiness::NeedMoreReallocations); - } + pub fn allocate(&mut self, actions: &[Action]) -> Result { + let mut total_result = AllocateResult::Ready; - for action in &actions { - let address = match action { - Action::NeonTransfer { target, .. } => target, - Action::EvmSelfDestruct { address, .. } | Action::EvmSetCode { address, .. } => { - address + for action in actions { + if let Action::EvmSetCode { address, code, .. } = action { + let result = ContractAccount::allocate( + *address, + code, + &self.rent, + &self.accounts, + Some(&self.keys), + )?; + + if result == AllocateResult::NeedMore { + total_result = AllocateResult::NeedMore; } - _ => continue, - }; - self.create_account_if_not_exists(address)?; + } } - let mut storage: HashMap> = - HashMap::with_capacity(actions.len()); + Ok(total_result) + } + + /// Takes an *immutable borrow* of Actions from the accumulated state changes and applies it. + pub fn apply_state_change(&mut self, actions: &Vector) -> Result<()> { + debug_print!("Applies begin"); + + let mut storage = HashMap::with_capacity(16); for action in actions { match action { - Action::NeonTransfer { + Action::Transfer { source, target, + chain_id, value, } => { - self.transfer_neon_tokens(&source, &target, value)?; - } - Action::NeonWithdraw { source, value } => { - let account = self.ethereum_account_mut(&source); - if account.balance < value { - return Err!(ProgramError::InsufficientFunds; "Account {} - insufficient funds, required = {}", source, value)?; - } + let mut source = self.balance_account(*source, *chain_id)?; + let mut target = self.create_balance_account(*target, *chain_id)?; + + source.increment_revision(&self.rent, &self.accounts)?; + target.increment_revision(&self.rent, &self.accounts)?; - account.balance -= value; + source.transfer(&mut target, *value)?; + } + Action::Burn { + source, + chain_id, + value, + } => { + let mut account = self.create_balance_account(*source, *chain_id)?; + account.increment_revision(&self.rent, &self.accounts)?; + account.burn(*value)?; } Action::EvmSetStorage { address, @@ -112,25 +104,30 @@ impl<'a> ProgramAccountStorage<'a> { value, } => { storage - .entry(address) - .or_insert_with(|| Vec::with_capacity(64)) - .push((index, value)); + .entry(*address) + .or_insert_with(|| HashMap::with_capacity(64)) + .insert(*index, *value); } - Action::EvmIncrementNonce { address } => { - let account = self.ethereum_account_mut(&address); - if account.trx_count == u64::MAX { - return Err!(ProgramError::InvalidAccountData; "Account {} - nonce overflow", account.address); - } - - account.trx_count += 1; + Action::EvmSetTransientStorage { .. } => { + // do nothing, transient storage is discarded at the end of the transaction } - Action::EvmSetCode { address, code } => { - self.deploy_contract(address, &code)?; + Action::EvmIncrementNonce { address, chain_id } => { + let mut account = self.create_balance_account(*address, *chain_id)?; + account.increment_nonce()?; } - Action::EvmSelfDestruct { address } => { - storage.remove(&address); - - self.delete_account(address)?; + Action::EvmSetCode { + address, + chain_id, + code, + } => { + ContractAccount::create( + *address, + *chain_id, + 0, + code, + &self.accounts, + Some(&self.keys), + )?; } Action::ExternalInstruction { program_id, @@ -139,330 +136,115 @@ impl<'a> ProgramAccountStorage<'a> { seeds, .. } => { - let seeds: Vec<&[u8]> = seeds.iter().map(|seed| &seed[..]).collect(); + let seeds = seeds + .iter() + .map(|s| s.iter().map(|s| s.as_slice()).collect::>()) + .collect::>(); + let seeds = seeds.iter().map(|s| s.as_slice()).collect::>(); let mut accounts_info = Vec::with_capacity(accounts.len() + 1); - accounts_info.push(self.solana_accounts[&program_id].clone()); - for meta in &accounts { - accounts_info.push(self.solana_accounts[&meta.pubkey].clone()); + let program = self.accounts.get(&program_id).clone(); + accounts_info.push(program); + + // Convert from persistent Vector to std::Vec, as required by the solana Instruction. + let mut ixn_accounts = accounts.to_vec(); + for meta in &mut ixn_accounts { + if meta.pubkey == FAKE_OPERATOR { + meta.pubkey = self.accounts.operator_key(); + } + let account = self.accounts.get(&meta.pubkey).clone(); + accounts_info.push(account); } let instruction = Instruction { - program_id, - accounts, - data, + program_id: *program_id, + accounts: ixn_accounts, + data: data.to_vec(), }; - invoke_signed_unchecked(&instruction, &accounts_info, &[&seeds])?; - } - } - } - - self.apply_storage(system_program, operator, storage)?; - debug_print!("Applies done"); - - Ok(AccountsReadiness::Ready) - } - fn rearrange_actions(actions: Vec) -> Vec { - // Find all the account addresses which are scheduled to EvmSelfDestruct - let accounts_to_destroy: std::collections::HashSet<_> = actions - .iter() - .filter_map(|action| match action { - Action::EvmSelfDestruct { address } => Some(*address), - _ => None, - }) - .collect(); - - // For accounts scheduled to Self Destroy only leave NeonTransfer and NeonWithdraw actions - let mut rearranged_actions = Vec::with_capacity(actions.len()); - let mut evm_self_destruct_actions = Vec::new(); - for action in actions { - match action { - // We always apply ExternalInstruction for Solana accounts - // and NeonTransfer + NeonWithdraw - Action::ExternalInstruction { .. } - | Action::NeonTransfer { .. } - | Action::NeonWithdraw { .. } => { - rearranged_actions.push(action); - } - // We remove EvmSetStorage|EvmIncrementNonce|EvmSetCode - // if account is scheduled for destroy - Action::EvmSetStorage { address, .. } - | Action::EvmSetCode { address, .. } - | Action::EvmIncrementNonce { address } => { - if !accounts_to_destroy.contains(&address) { - rearranged_actions.push(action); + if !seeds.is_empty() { + invoke_signed_unchecked(&instruction, &accounts_info, &seeds)?; + } else { + invoke_unchecked(&instruction, &accounts_info)?; } } - // Move EvmSelfDestruct to a separate Vec - Action::EvmSelfDestruct { .. } => { - evm_self_destruct_actions.push(action); - } } } - // Constructing compound list of actions, - // first: we execute everything except SelfDestruct, - // second: execute all SelfDestructs - rearranged_actions.append(&mut evm_self_destruct_actions); - rearranged_actions + self.apply_storage(storage)?; + + debug_print!("Applies done"); + + Ok(()) } - fn apply_storage( - &mut self, - system_program: &program::System<'a>, - operator: &Operator<'a>, - storage: HashMap>, - ) -> Result<(), ProgramError> { + fn apply_storage(&mut self, storage: HashMap>) -> Result<()> { const STATIC_STORAGE_LIMIT: U256 = U256::new(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u128); - let mut required_account_transfers = std::collections::HashMap::new(); - for (address, storage) in storage { - let contract: &EthereumAccount<'a> = &self.ethereum_accounts[&address]; - let contract_data = contract.contract_data().expect("Contract expected"); + let mut contract: Option = None; + + let mut infinite_values: HashMap> = + HashMap::with_capacity(storage.len()); for (index, value) in storage { if index < STATIC_STORAGE_LIMIT { + let contract = contract.get_or_insert_with(|| { + self.contract_account(address) + .expect("contract already created") + }); + // Static Storage - Write into contract account - let index: usize = index.as_usize() * 32; - contract_data.storage()[index..index + 32].copy_from_slice(&value); + let index: usize = index.as_usize(); + contract.set_storage_value(index, &value); } else { // Infinite Storage - Write into separate account let subindex = (index & 0xFF).as_u8(); let index = index & !U256::new(0xFF); - match self.storage_accounts.entry((contract.address, index)) { - Entry::Vacant(entry) => { - let storage_address = EthereumStorageAddress::new( - self.program_id, - contract.info.key, - &index, - ); - let storage_account = self.solana_accounts.get(&storage_address.pubkey()) - .ok_or_else(|| E!(ProgramError::InvalidArgument; "Account {} - storage account not found", storage_address.pubkey()))?; - - if !solana_program::system_program::check_id(storage_account.owner) { - return Err!(ProgramError::InvalidAccountData; "Account {} - expected system or program owned", storage_address.pubkey()); - } - - if value == [0_u8; 32] { - continue; - } - - let storage = EthereumStorage::create( - contract, - storage_account, - &storage_address, - index, - subindex, - &value, - operator, - system_program, - )?; - - entry.insert(storage); - } - Entry::Occupied(mut entry) => { - let storage = entry.get_mut(); - storage.set(subindex, &value, &mut required_account_transfers)?; - } - } + infinite_values + .entry(index) + .or_insert_with(|| HashMap::with_capacity(32)) + .insert(subindex, value); } } - } - for (_key, (info, required_lamports)) in required_account_transfers { - system_program.transfer(operator, &info, required_lamports)?; - } - - Ok(()) - } + if let Some(mut contract) = contract { + contract.increment_revision(&self.rent, &self.accounts)?; + } - fn process_accounts_operations( - &mut self, - system_program: &program::System<'a>, - neon_program: &program::Neon<'a>, - operator: &Operator<'a>, - accounts_operations: AccountsOperations, - ) -> Result { - let mut accounts_readiness = AccountsReadiness::Ready; - for (address, operation) in accounts_operations { - let (solana_address, bump_seed) = address.find_solana_address(self.program_id); - let solana_account = self.solana_account(&solana_address).ok_or_else(|| { - E!( - ProgramError::UninitializedAccount; - "Account {} - corresponding Solana account was not provided", - address - ) - })?; - match operation { - AccountOperation::Create { space } => { - debug_print!("Creating account (space = {})", space); - EthereumAccount::create_account( - system_program, - neon_program.key, - operator, - &address, - solana_account, - bump_seed, - MAX_PERMITTED_DATA_INCREASE.min(space), - )?; + for (index, values) in infinite_values { + let cell_address = self.keys.storage_cell_address(&crate::ID, address, index); - if space > MAX_PERMITTED_DATA_INCREASE { - accounts_readiness = AccountsReadiness::NeedMoreReallocations; - } - } + let account = self.accounts.get(cell_address.pubkey()); - AccountOperation::Resize { from, to } => { - debug_print!("Resizing account (from = {}, to = {})", from, to); - - assert_eq!(solana_account.owner, self.program_id); - - let rent = Rent::get()?; - let lamports_needed = rent - .minimum_balance(to.min(from.saturating_add(MAX_PERMITTED_DATA_INCREASE))); - let lamports_current = solana_account.lamports(); - if lamports_current < lamports_needed { - invoke( - &system_instruction::transfer( - operator.key, - solana_account.key, - lamports_needed.saturating_sub(lamports_current), - ), - &[ - (*operator.info).clone(), - solana_account.clone(), - (*system_program).clone(), - ], - )?; - } + if system_program::check_id(account.owner) { + let (_, bump) = self.keys.contract_with_bump_seed(&crate::ID, address); + let sign: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], address.as_bytes(), &[bump]]; - let max_possible_space_per_instruction = - to.min(from + MAX_PERMITTED_DATA_INCREASE); - solana_account.realloc(max_possible_space_per_instruction, false)?; + let len = values.len(); + let mut storage = + StorageCell::create(cell_address, len, &self.accounts, sign, &self.rent)?; + let mut cells = storage.cells_mut(); - if max_possible_space_per_instruction < to { - accounts_readiness = AccountsReadiness::NeedMoreReallocations; + assert_eq!(cells.len(), len); + for (cell, (subindex, value)) in cells.iter_mut().zip(values) { + cell.subindex = subindex; + cell.value = value; + } + } else { + let mut storage = StorageCell::from_account(&crate::ID, account.clone())?; + for (subindex, value) in values { + storage.update(subindex, &value)?; } - } - }; - } - - Ok(accounts_readiness) - } - - /// Delete all data in the account. - fn delete_account(&mut self, address: Address) -> ProgramResult { - let account = self.ethereum_account_mut(&address); - - account.trx_count = 0; - account.generation = account.generation.checked_add(1) - .ok_or_else(|| E!(ProgramError::InvalidInstructionData; "Account {} - generation overflow", address))?; - - if let Some(contract) = account.contract_data() { - contract.extension_borrow_mut().fill(0); - } - - account.code_size = 0; - - Ok(()) - } - - fn deploy_contract(&mut self, address: Address, code: &[u8]) -> ProgramResult { - let account = self.ethereum_accounts.get_mut(&address).ok_or_else( - || E!(ProgramError::UninitializedAccount; "Account {} - is not initialized", address), - )?; - - assert_eq!( - account.code_size, 0, - "Contract already deployed to address {} (code_size = {})!", - account.address, account.code_size, - ); - - let space_needed = EthereumAccount::space_needed(code.len()); - let space_actual = account.info.data_len(); - assert!( - space_actual >= space_needed, - "Not enough space for account deployment at address {} \ - (code size: {}, space needed: {}, actual space: {})", - account.address, - code.len(), - space_needed, - space_actual, - ); - - account.code_size = code - .len() - .try_into() - .expect("code.len() never exceeds u32::max"); - - let contract = account - .contract_data() - .expect("Contract data must be available at this point"); - - contract.code().copy_from_slice(code); - - Ok(()) - } - - fn transfer_neon_tokens( - &mut self, - source: &Address, - target: &Address, - value: U256, - ) -> ProgramResult { - debug_print!("Transfer {} NEONs from {} to {}", value, source, target); - - if source == target { - return Ok(()); - } - - if !self.ethereum_accounts.contains_key(source) { - return Err!(ProgramError::InvalidArgument; "Account {} - expect initialized", source); - } - if !self.ethereum_accounts.contains_key(target) { - return Err!(ProgramError::InvalidArgument; "Account {} - expect initialized", source); - } - - if self.balance(source) < value { - return Err!(ProgramError::InsufficientFunds; "Account {} - insufficient funds, required = {}", source, value); - } - - self.ethereum_account_mut(source).balance -= value; - self.ethereum_account_mut(target).balance += value; - - Ok(()) - } - fn create_account_if_not_exists(&mut self, address: &Address) -> ProgramResult { - if self.ethereum_accounts.contains_key(address) { - return Ok(()); + storage.sync_lamports(&self.rent, &self.accounts)?; + storage.increment_revision(&self.rent, &self.accounts)?; + }; + } } - let (solana_address, bump_seed) = address.find_solana_address(self.program_id); - let info = self.solana_account(&solana_address).ok_or_else(|| { - E!( - ProgramError::InvalidArgument; - "Account {} not found in the list of Solana accounts", - solana_address - ) - })?; - - let ether_account = EthereumAccount::init( - self.program_id, - info, - ether_account::Data { - address: *address, - bump_seed, - ..Default::default() - }, - )?; - - self.ethereum_accounts - .insert(ether_account.address, ether_account); - Ok(()) } } diff --git a/evm_loader/program/src/account_storage/backend.rs b/evm_loader/program/src/account_storage/backend.rs index b38645242..9b33c2c36 100644 --- a/evm_loader/program/src/account_storage/backend.rs +++ b/evm_loader/program/src/account_storage/backend.rs @@ -1,26 +1,57 @@ -use crate::account::EthereumAccount; -use crate::account_storage::{AccountStorage, ProgramAccountStorage}; +use crate::account_storage::{AccountStorage, LogCollector, ProgramAccountStorage}; use crate::config::STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT; +use crate::error::Result; use crate::executor::OwnedAccountInfo; use crate::types::Address; use ethnum::U256; use solana_program::account_info::AccountInfo; -use solana_program::{pubkey::Pubkey, sysvar::slot_hashes}; +use solana_program::{pubkey::Pubkey, rent::Rent, sysvar::slot_hashes}; use std::convert::TryInto; -use super::find_slot_hash; - -impl<'a> AccountStorage for ProgramAccountStorage<'a> { - fn neon_token_mint(&self) -> &Pubkey { - &crate::config::token_mint::ID +use crate::debug::log_data; + +impl LogCollector for ProgramAccountStorage<'_> { + fn collect_log( + &mut self, + address: &[u8; 20], + topics: [[u8; 32]; N], + data: &[u8], + ) { + match N { + 0 => log_data(&[b"LOG0", address, &[0], data]), + 1 => log_data(&[b"LOG1", address, &[1], &topics[0], data]), + 2 => log_data(&[b"LOG2", address, &[2], &topics[0], &topics[1], data]), + 3 => log_data(&[ + b"LOG3", + address, + &[3], + &topics[0], + &topics[1], + &topics[2], + data, + ]), + 4 => log_data(&[ + b"LOG4", + address, + &[4], + &topics[0], + &topics[1], + &topics[2], + &topics[3], + data, + ]), + _ => unreachable!(), + } } +} +impl<'a> AccountStorage for ProgramAccountStorage<'a> { fn program_id(&self) -> &Pubkey { - self.program_id + &crate::ID } - fn operator(&self) -> &Pubkey { - self.operator + fn operator(&self) -> Pubkey { + self.accounts.operator_key() } fn block_number(&self) -> U256 { @@ -34,131 +65,114 @@ impl<'a> AccountStorage for ProgramAccountStorage<'a> { .expect("Timestamp is positive") } - fn block_hash(&self, slot: u64) -> [u8; 32] { - let slot_hashes_account = self - .solana_accounts - .get(&slot_hashes::ID) - .unwrap_or_else(|| { - panic!( - "Trying to get slot hash info without providing sysvar account: {}", - slot_hashes::ID - ) - }); + fn rent(&self) -> &Rent { + &self.rent + } - let slot_hashes_data = slot_hashes_account.data.borrow(); - find_slot_hash(slot, &slot_hashes_data[..]) + fn return_data(&self) -> Option<(Pubkey, Vec)> { + solana_program::program::get_return_data().map(|res| (res.0, res.1)) } - fn exists(&self, address: &Address) -> bool { - self.ethereum_accounts.contains_key(address) + fn set_return_data(&mut self, data: &[u8]) { + solana_program::program::set_return_data(data); } - fn nonce(&self, address: &Address) -> u64 { - self.ethereum_account(address) - .map_or(0_u64, |a| a.trx_count) + fn block_hash(&self, slot: u64) -> [u8; 32] { + let slot_hashes_account = self.accounts.get(&slot_hashes::ID); + let slot_hashes_data = slot_hashes_account.data.borrow(); + + super::block_hash::find_slot_hash(slot, &slot_hashes_data[..]) } - fn balance(&self, address: &Address) -> U256 { - self.ethereum_account(address) - .map_or(U256::ZERO, |a| a.balance) + fn nonce(&self, address: Address, chain_id: u64) -> u64 { + self.balance_account(address, chain_id) + .map_or(0_u64, |a| a.nonce()) } - fn code_size(&self, address: &Address) -> usize { - self.ethereum_account(address) - .map_or(0, |a| a.code_size as usize) + fn balance(&self, address: Address, chain_id: u64) -> U256 { + self.balance_account(address, chain_id) + .map_or(U256::ZERO, |a| a.balance()) } - fn code_hash(&self, address: &Address) -> [u8; 32] { - use solana_program::keccak::hash; + fn is_valid_chain_id(&self, chain_id: u64) -> bool { + crate::config::CHAIN_ID_LIST + .binary_search_by_key(&chain_id, |c| c.0) + .is_ok() + } - // https://eips.ethereum.org/EIPS/eip-1052 - // https://eips.ethereum.org/EIPS/eip-161 - if self.code_size(address) == 0 { - if self.nonce(address) == 0 && self.balance(address) == 0 { - // non-existent account - return <[u8; 32]>::default(); - } + fn chain_id_to_token(&self, chain_id: u64) -> Pubkey { + let index = crate::config::CHAIN_ID_LIST + .binary_search_by_key(&chain_id, |c| c.0) + .unwrap(); - // account without code - return hash(&[]).to_bytes(); - } + crate::config::CHAIN_ID_LIST[index].2 + } - self.ethereum_account(address) - .and_then(EthereumAccount::contract_data) - .map(|contract| hash(&contract.code())) - .unwrap_or_default() - .to_bytes() + fn default_chain_id(&self) -> u64 { + crate::config::DEFAULT_CHAIN_ID } - fn code(&self, address: &Address) -> crate::evm::Buffer { - use crate::evm::Buffer; + fn contract_chain_id(&self, address: Address) -> Result { + let contract = self.contract_account(address)?; + Ok(contract.chain_id()) + } - if let Some(account) = self.ethereum_account(address) { - if account.code_size() == 0 { - return Buffer::empty(); - } + fn contract_pubkey(&self, address: Address) -> (Pubkey, u8) { + self.keys + .contract_with_bump_seed(self.program_id(), address) + } + + fn balance_pubkey(&self, address: Address, chain_id: u64) -> (Pubkey, u8) { + self.keys + .balance_with_bump_seed(self.program_id(), address, chain_id) + } - Buffer::from_account(account.info, account.code_location()) + fn storage_cell_pubkey(&self, address: Address, index: U256) -> Pubkey { + if index < U256::from(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u64) { + self.keys.contract(self.program_id(), address) } else { - Buffer::empty() + self.keys.storage_cell(self.program_id(), address, index) } } - fn generation(&self, address: &Address) -> u32 { - self.ethereum_account(address) - .map_or(0_u32, |c| c.generation) + fn code_size(&self, address: Address) -> usize { + self.contract_account(address).map_or(0, |a| a.code_len()) + } + + fn code(&self, address: Address) -> crate::evm::Buffer { + self.contract_account(address) + .map_or_else(|_| crate::evm::Buffer::empty(), |a| a.code_buffer()) } - fn storage(&self, address: &Address, index: &U256) -> [u8; 32] { - if *index < U256::from(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT) { - let index: usize = index.as_usize() * 32; + fn storage(&self, address: Address, index: U256) -> [u8; 32] { + if index < U256::from(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u64) { + let index: usize = index.as_usize(); return self - .ethereum_account(address) - .and_then(EthereumAccount::contract_data) - .map(|c| c.storage()[index..index + 32].try_into().unwrap()) + .contract_account(address) + .map(|c| c.storage_value(index)) .unwrap_or_default(); } let subindex = (index & 0xFF).as_u8(); let index = index & !U256::new(0xFF); - self.ethereum_storage(*address, index) - .map_or_else(<[u8; 32]>::default, |a| a.get(subindex)) + self.storage_cell(address, index) + .map(|a| a.get(subindex)) + .unwrap_or_default() } fn clone_solana_account(&self, address: &Pubkey) -> OwnedAccountInfo { - let info = self.solana_accounts[address]; - OwnedAccountInfo::from_account_info(self.program_id, info) + // This is used to emulate external instruction + // One of instruction accounts can be operator + let info = self.accounts.get(address); + OwnedAccountInfo::from_account_info(self.program_id(), info) } fn map_solana_account(&self, address: &Pubkey, action: F) -> R where F: FnOnce(&AccountInfo) -> R, { - let info = self.solana_accounts[address]; + let info = self.accounts.get(address); action(info) } - - fn solana_account_space(&self, address: &Address) -> Option { - let (pubkey, _) = self.solana_address(address); - let info = self.solana_accounts[&pubkey]; - - if solana_program::system_program::check_id(info.owner) { - return None; - } - - assert_eq!(info.owner, self.program_id); - Some(info.data_len()) - } - - fn solana_address(&self, address: &Address) -> (Pubkey, u8) { - self.ethereum_accounts.get(address).map_or_else( - || address.find_solana_address(self.program_id), - |a| (*a.info.key, a.bump_seed), - ) - } - - fn chain_id(&self) -> u64 { - crate::config::CHAIN_ID - } } diff --git a/evm_loader/program/src/account_storage/base.rs b/evm_loader/program/src/account_storage/base.rs index 1b364c27a..abcef7079 100644 --- a/evm_loader/program/src/account_storage/base.rs +++ b/evm_loader/program/src/account_storage/base.rs @@ -1,154 +1,112 @@ -use crate::account::ether_storage::EthereumStorageAddress; -use crate::account::{program, EthereumAccount, EthereumStorage, Operator, TAG_EMPTY}; -use crate::account_storage::{AccountStorage, ProgramAccountStorage}; -use crate::types::Address; +use std::collections::HashSet; + +use crate::account::{ + AccountsDB, BalanceAccount, ContractAccount, Operator, StorageCell, Treasury, +}; +use crate::account_storage::ProgramAccountStorage; +use crate::config::DEFAULT_CHAIN_ID; +use crate::error::Result; +use crate::types::{Address, Transaction}; use ethnum::U256; -use solana_program::account_info::AccountInfo; -use solana_program::clock::Clock; -use solana_program::program_error::ProgramError; -use solana_program::pubkey::Pubkey; -use solana_program::system_program; -use solana_program::sysvar::Sysvar; -use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; +use solana_program::{clock::Clock, rent::Rent, system_program, sysvar::Sysvar}; -impl<'a> ProgramAccountStorage<'a> { - pub fn new( - program_id: &'a Pubkey, - operator: &Operator<'a>, - system_program: Option<&program::System<'a>>, - accounts: &'a [AccountInfo<'a>], - ) -> Result { - debug_print!("ProgramAccountStorage::new"); - - let mut solana_accounts = accounts - .iter() - .map(|a| (a.key, a)) - .collect::>(); - - solana_accounts.insert(operator.key, operator.info); - if let Some(system) = system_program { - solana_accounts.insert(system.key, system.into()); - } - - let mut ethereum_accounts = HashMap::with_capacity(accounts.len()); - let mut storage_accounts = HashMap::with_capacity(accounts.len()); - - for &account_info in solana_accounts.values() { - if account_info.owner != program_id { - continue; - } - - match crate::account::tag(program_id, account_info) { - Ok(EthereumAccount::TAG) => { - let account = EthereumAccount::from_account(program_id, account_info)?; - ethereum_accounts.insert(account.address, account); - } - Ok(EthereumStorage::TAG) => { - let account = EthereumStorage::from_account(program_id, account_info)?; - storage_accounts.insert((account.address, account.index), account); - } - Ok(_) | Err(_) => continue, - } - } - - for storage in storage_accounts.values_mut() { - let owner = ðereum_accounts[&storage.address]; - if storage.generation != owner.generation { - storage.clear(owner.generation, operator)?; - } - } +use super::keys_cache::KeysCache; +use super::AccountStorage; +impl<'a> ProgramAccountStorage<'a> { + pub fn new(accounts: AccountsDB<'a>) -> Result { Ok(Self { - program_id, - operator: operator.key, clock: Clock::get()?, - solana_accounts, - ethereum_accounts, - empty_ethereum_accounts: RefCell::new(HashSet::new()), - storage_accounts, - empty_storage_accounts: RefCell::new(HashSet::new()), + rent: Rent::get()?, + accounts, + keys: KeysCache::new(), + synced_modified_contracts: HashSet::new(), }) } - pub fn solana_account(&self, solana_address: &Pubkey) -> Option<&'a AccountInfo<'a>> { - self.solana_accounts.get(solana_address).copied() + pub fn operator(&self) -> &Operator<'a> { + self.accounts.operator() } - pub fn ethereum_storage(&self, address: Address, index: U256) -> Option<&EthereumStorage<'a>> { - let key = (address, index); + pub fn treasury(&self) -> &Treasury<'a> { + self.accounts.treasury() + } - if let Some(account) = self.storage_accounts.get(&key) { - return Some(account); - } + pub fn db(&self) -> &AccountsDB<'a> { + &self.accounts + } - let mut empty_accounts = self.empty_storage_accounts.borrow_mut(); - if empty_accounts.contains(&key) { - return None; - } + pub fn storage_cell(&self, address: Address, index: U256) -> Result> { + let pubkey = self.keys.storage_cell(&crate::ID, address, index); - let storage_address = - EthereumStorageAddress::new(self.program_id, &self.solana_address(&address).0, &index); - if let Some(&account) = self.solana_accounts.get(&storage_address.pubkey()) { - assert!(solana_program::system_program::check_id(account.owner)); + let account = self.accounts.get(&pubkey); + let result = StorageCell::from_account(&crate::ID, account.clone()); - empty_accounts.insert(key); - return None; + if result.is_err() { + // Check that account is not in a legacy format + // Correct account can ether be owned by System or be valid StorageCell + assert!(system_program::check_id(account.owner)); } - panic!( - "Storage account {} {} (solana address {}) must be present in the transaction", - address, - index, - storage_address.pubkey() - ); + result } - pub fn ethereum_account(&self, address: &Address) -> Option<&EthereumAccount<'a>> { - if let Some(account) = self.ethereum_accounts.get(address) { - return Some(account); - } - - let mut empty_accounts = self.empty_ethereum_accounts.borrow_mut(); - if empty_accounts.contains(address) { - return None; - } + pub fn contract_account(&self, address: Address) -> Result> { + let pubkey = self.keys.contract(&crate::ID, address); - let (solana_address, _bump_seed) = address.find_solana_address(self.program_id); - if let Some(account) = self.solana_accounts.get(&solana_address) { - assert!( - self.is_account_empty(account), - "Empty ethereum account {address} must belong to the system program or be uninitialized" - ); + let account = self.accounts.get(&pubkey); + let result = ContractAccount::from_account(&crate::ID, account.clone()); - empty_accounts.insert(*address); - return None; + if result.is_err() { + let legacy_tag = crate::account::legacy::TAG_ACCOUNT_CONTRACT_DEPRECATED; + assert!(crate::account::validate_tag(&crate::ID, account, legacy_tag).is_err()); } - panic!("Ethereum account {address} (solana address {solana_address}) must be present in the transaction"); + result } - pub fn ethereum_account_mut(&mut self, address: &Address) -> &mut EthereumAccount<'a> { - self.ethereum_accounts.get_mut(address).unwrap() // mutable accounts always present - } + pub fn balance_account(&self, address: Address, chain_id: u64) -> Result> { + let pubkey = self.keys.balance(&crate::ID, address, chain_id); + + let account = self.accounts.get(&pubkey); + let result = BalanceAccount::from_account(&crate::ID, account.clone()); - pub fn block_accounts(&mut self, block: bool) { - for account in &mut self.ethereum_accounts.values_mut() { - account.rw_blocked = block; + if result.is_err() && (chain_id == DEFAULT_CHAIN_ID) { + let contract_pubkey = self.keys.contract(&crate::ID, address); + + let contract = self.accounts.get(&contract_pubkey); + + let legacy_tag = crate::account::legacy::TAG_ACCOUNT_CONTRACT_DEPRECATED; + assert!(crate::account::validate_tag(&crate::ID, contract, legacy_tag).is_err()); } + + result } - pub fn check_for_blocked_accounts(&self) -> Result<(), ProgramError> { - for ethereum_account in self.ethereum_accounts.values() { - ethereum_account.check_blocked()?; - } + pub fn create_balance_account( + &self, + address: Address, + chain_id: u64, + ) -> Result> { + let account = BalanceAccount::create( + address, + chain_id, + &self.accounts, + Some(&self.keys), + &self.rent, + )?; - Ok(()) + Ok(account) } - pub fn is_account_empty(&self, account: &AccountInfo) -> bool { - system_program::check_id(account.owner) - || (account.owner == self.program_id() - && (account.data_is_empty() || account.data.borrow()[0] == TAG_EMPTY)) + pub fn origin( + &self, + address: Address, + transaction: &Transaction, + ) -> Result> { + let chain_id = transaction + .chain_id() + .unwrap_or_else(|| self.default_chain_id()); + self.create_balance_account(address, chain_id) } } diff --git a/evm_loader/program/src/account_storage/block_hash.rs b/evm_loader/program/src/account_storage/block_hash.rs new file mode 100644 index 000000000..dd06bd2b2 --- /dev/null +++ b/evm_loader/program/src/account_storage/block_hash.rs @@ -0,0 +1,69 @@ +use solana_program::slot_history::Slot; +use std::cmp::Ordering; + +#[must_use] +pub fn find_slot_hash(value: Slot, slot_hashes_data: &[u8]) -> [u8; 32] { + let slot_hashes_len = u64::from_le_bytes(slot_hashes_data[..8].try_into().unwrap()); + + // copy-paste from slice::binary_search + let mut size = usize::try_from(slot_hashes_len).unwrap() - 1; + let mut left = 0; + let mut right = size; + + while left < right { + let mid = left + size / 2; + let offset = mid * 40 + 8; // +8 - the first 8 bytes for the len of vector + + let slot = u64::from_le_bytes(slot_hashes_data[offset..][..8].try_into().unwrap()); + let cmp = value.cmp(&slot); + + // The reason why we use if/else control flow rather than match + // is because match reorders comparison operations, which is perf sensitive. + // This is x86 asm for u8: https://rust.godbolt.org/z/8Y8Pra. + if cmp == Ordering::Less { + left = mid + 1; + } else if cmp == Ordering::Greater { + right = mid; + } else { + return slot_hashes_data[(offset + 8)..][..32].try_into().unwrap(); + } + + size = right - left; + } + + generate_fake_slot_hash(value) +} + +#[must_use] +pub fn generate_fake_slot_hash(slot: Slot) -> [u8; 32] { + let slot_bytes: [u8; 8] = slot.to_be_bytes(); + let mut initial = 0; + for b in slot_bytes { + if b != 0 { + break; + } + initial += 1; + } + let slot_slice = &slot_bytes[initial..]; + let slot_slice_len = slot_slice.len(); + let mut hash = [255; 32]; + hash[32 - slot_slice_len - 1] = 0; + hash[(32 - slot_slice_len)..].copy_from_slice(slot_slice); + hash +} + +#[test] +fn test_generate_fake_slot_hash() { + let slot = 0x46; + let mut expected: [u8; 32] = [255; 32]; + expected[30] = 0; + expected[31] = 0x46; + assert_eq!(generate_fake_slot_hash(slot), expected); + + let slot = 0x3e8; + let mut expected: [u8; 32] = [255; 32]; + expected[29] = 0; + expected[30] = 0x03; + expected[31] = 0xe8; + assert_eq!(generate_fake_slot_hash(slot), expected); +} diff --git a/evm_loader/program/src/account_storage/keys_cache.rs b/evm_loader/program/src/account_storage/keys_cache.rs new file mode 100644 index 000000000..a1922a980 --- /dev/null +++ b/evm_loader/program/src/account_storage/keys_cache.rs @@ -0,0 +1,96 @@ +use std::{cell::RefCell, collections::HashMap}; + +use ethnum::U256; +use solana_program::pubkey::Pubkey; + +use crate::{account::StorageCellAddress, types::Address}; + +type ContractKey = Address; +type BalanceKey = (Address, u64); +type StorageKey = (Address, U256); + +pub struct KeysCache { + contracts: RefCell>, + balances: RefCell>, + storage_cells: RefCell>, +} + +impl KeysCache { + #[must_use] + pub fn new() -> Self { + Self { + contracts: RefCell::new(HashMap::with_capacity(8)), + balances: RefCell::new(HashMap::with_capacity(8)), + storage_cells: RefCell::new(HashMap::with_capacity(32)), + } + } + + #[must_use] + pub fn contract_with_bump_seed(&self, program_id: &Pubkey, address: Address) -> (Pubkey, u8) { + *self + .contracts + .borrow_mut() + .entry(address) + .or_insert_with_key(|a| a.find_solana_address(program_id)) + } + + #[must_use] + pub fn contract(&self, program_id: &Pubkey, address: Address) -> Pubkey { + self.contract_with_bump_seed(program_id, address).0 + } + + #[must_use] + pub fn balance_with_bump_seed( + &self, + program_id: &Pubkey, + address: Address, + chain_id: u64, + ) -> (Pubkey, u8) { + *self + .balances + .borrow_mut() + .entry((address, chain_id)) + .or_insert_with_key(|(a, chain_id)| a.find_balance_address(program_id, *chain_id)) + } + + #[must_use] + pub fn balance(&self, program_id: &Pubkey, address: Address, chain_id: u64) -> Pubkey { + self.balance_with_bump_seed(program_id, address, chain_id).0 + } + + #[must_use] + pub fn storage_cell(&self, program_id: &Pubkey, address: Address, index: U256) -> Pubkey { + *self + .storage_cells + .borrow_mut() + .entry((address, index)) + .or_insert_with(|| { + let base = self.contract(program_id, address); + StorageCellAddress::new(program_id, &base, &index) + }) + .pubkey() + } + + #[must_use] + pub fn storage_cell_address( + &self, + program_id: &Pubkey, + address: Address, + index: U256, + ) -> StorageCellAddress { + *self + .storage_cells + .borrow_mut() + .entry((address, index)) + .or_insert_with(|| { + let base = self.contract(program_id, address); + StorageCellAddress::new(program_id, &base, &index) + }) + } +} + +impl Default for KeysCache { + fn default() -> Self { + Self::new() + } +} diff --git a/evm_loader/program/src/account_storage/mod.rs b/evm_loader/program/src/account_storage/mod.rs index b2b47dbcf..5efd41264 100644 --- a/evm_loader/program/src/account_storage/mod.rs +++ b/evm_loader/program/src/account_storage/mod.rs @@ -1,209 +1,132 @@ -use crate::account::{EthereumAccount, EthereumStorage}; -use crate::executor::{Action, OwnedAccountInfo}; +use crate::executor::OwnedAccountInfo; use crate::types::Address; +use crate::{error::Result, types::Vector}; use ethnum::U256; -use solana_program::account_info::AccountInfo; -use solana_program::clock::Clock; -use solana_program::pubkey::Pubkey; -use solana_program::slot_history::Slot; -use std::cell::RefCell; -use std::cmp::Ordering; -use std::collections::{HashMap, HashSet}; - +use maybe_async::maybe_async; +use solana_program::{ + account_info::AccountInfo, instruction::Instruction, pubkey, pubkey::Pubkey, rent::Rent, +}; +#[cfg(target_os = "solana")] +use {crate::account::AccountsDB, solana_program::clock::Clock}; + +#[cfg(target_os = "solana")] mod apply; +#[cfg(target_os = "solana")] mod backend; +#[cfg(target_os = "solana")] mod base; +#[cfg(target_os = "solana")] +mod synced; -#[derive(Debug)] -pub enum AccountOperation { - Create { space: usize }, - - Resize { from: usize, to: usize }, -} +mod block_hash; +pub use block_hash::find_slot_hash; -pub type AccountsOperations = Vec<(Address, AccountOperation)>; - -#[derive(Debug, PartialEq, Eq)] -pub enum AccountsReadiness { - Ready, - NeedMoreReallocations, -} +mod keys_cache; +pub use keys_cache::KeysCache; +#[cfg(target_os = "solana")] pub struct ProgramAccountStorage<'a> { - program_id: &'a Pubkey, - operator: &'a Pubkey, clock: Clock, - - solana_accounts: HashMap<&'a Pubkey, &'a AccountInfo<'a>>, - - ethereum_accounts: HashMap>, - empty_ethereum_accounts: RefCell>, - - storage_accounts: HashMap<(Address, U256), EthereumStorage<'a>>, - empty_storage_accounts: RefCell>, + rent: Rent, + accounts: AccountsDB<'a>, + keys: keys_cache::KeysCache, + synced_modified_contracts: std::collections::HashSet, } +pub const FAKE_OPERATOR: Pubkey = pubkey!("neonoperator1111111111111111111111111111111"); + /// Account storage /// Trait to access account info -pub trait AccountStorage { - /// Get `NEON` token mint - fn neon_token_mint(&self) -> &Pubkey; - +#[maybe_async(?Send)] +pub trait AccountStorage: LogCollector { /// Get `NeonEVM` program id fn program_id(&self) -> &Pubkey; - /// Get operator pubkey - fn operator(&self) -> &Pubkey; + fn operator(&self) -> Pubkey; /// Get block number fn block_number(&self) -> U256; /// Get block timestamp fn block_timestamp(&self) -> U256; /// Get block hash - fn block_hash(&self, number: u64) -> [u8; 32]; - /// Get chain id - fn chain_id(&self) -> u64; + async fn block_hash(&self, number: u64) -> [u8; 32]; + + /// Get rent info + fn rent(&self) -> &Rent; + + /// Get return data from Solana + fn return_data(&self) -> Option<(Pubkey, Vec)>; + + /// Set return data to Solana + fn set_return_data(&mut self, data: &[u8]); - /// Check if ethereum account exists - fn exists(&self, address: &Address) -> bool; /// Get account nonce - fn nonce(&self, address: &Address) -> u64; + async fn nonce(&self, address: Address, chain_id: u64) -> u64; /// Get account balance - fn balance(&self, address: &Address) -> U256; + async fn balance(&self, address: Address, chain_id: u64) -> U256; + + fn is_valid_chain_id(&self, chain_id: u64) -> bool; + fn chain_id_to_token(&self, chain_id: u64) -> Pubkey; + fn default_chain_id(&self) -> u64; + + /// Get contract chain_id + async fn contract_chain_id(&self, address: Address) -> Result; + + /// Get contract solana address + fn contract_pubkey(&self, address: Address) -> (Pubkey, u8); + /// Get balance solana address + fn balance_pubkey(&self, address: Address, chain_id: u64) -> (Pubkey, u8); + /// Get cell solana address + fn storage_cell_pubkey(&self, address: Address, index: U256) -> Pubkey; /// Get code size - fn code_size(&self, address: &Address) -> usize; - /// Get code hash - fn code_hash(&self, address: &Address) -> [u8; 32]; + async fn code_size(&self, address: Address) -> usize; /// Get code data - fn code(&self, address: &Address) -> crate::evm::Buffer; - /// Get contract generation - fn generation(&self, address: &Address) -> u32; + async fn code(&self, address: Address) -> crate::evm::Buffer; /// Get data from storage - fn storage(&self, address: &Address, index: &U256) -> [u8; 32]; + async fn storage(&self, address: Address, index: U256) -> [u8; 32]; /// Clone existing solana account - fn clone_solana_account(&self, address: &Pubkey) -> OwnedAccountInfo; + async fn clone_solana_account(&self, address: &Pubkey) -> OwnedAccountInfo; /// Map existing solana account - fn map_solana_account(&self, address: &Pubkey, action: F) -> R + async fn map_solana_account(&self, address: &Pubkey, action: F) -> R where F: FnOnce(&AccountInfo) -> R; - - /// Resolve account solana address and bump seed - fn solana_address(&self, address: &Address) -> (Pubkey, u8) { - address.find_solana_address(self.program_id()) - } - - /// Solana account data len - fn solana_account_space(&self, address: &Address) -> Option; - - fn calc_accounts_operations(&self, actions: &[Action]) -> AccountsOperations { - let mut accounts = HashMap::new(); - for action in actions { - let (address, code_size) = match action { - Action::NeonTransfer { target, .. } => (target, 0), - Action::EvmSelfDestruct { address } => (address, 0), - Action::EvmSetCode { address, code, .. } => (address, code.len()), - _ => continue, - }; - - let space_needed = EthereumAccount::space_needed(code_size); - if let Some(max_size) = accounts.get_mut(&address) { - *max_size = space_needed.max(*max_size); - continue; - } - accounts.insert(address, space_needed); - } - - accounts - .into_iter() - .filter_map( - |(address, space_needed)| match self.solana_account_space(address) { - None => Some(( - *address, - AccountOperation::Create { - space: space_needed, - }, - )), - Some(space_current) if space_current < space_needed => Some(( - *address, - AccountOperation::Resize { - from: space_current, - to: space_needed, - }, - )), - _ => None, - }, - ) - .collect() - } -} - -#[must_use] -pub fn find_slot_hash(value: Slot, slot_hashes_data: &[u8]) -> [u8; 32] { - let slot_hashes_len = u64::from_le_bytes(slot_hashes_data[..8].try_into().unwrap()); - - // copy-paste from slice::binary_search - let mut size = usize::try_from(slot_hashes_len).unwrap() - 1; - let mut left = 0; - let mut right = size; - - while left < right { - let mid = left + size / 2; - let offset = mid * 40 + 8; // +8 - the first 8 bytes for the len of vector - - let slot = u64::from_le_bytes(slot_hashes_data[offset..][..8].try_into().unwrap()); - let cmp = value.cmp(&slot); - - // The reason why we use if/else control flow rather than match - // is because match reorders comparison operations, which is perf sensitive. - // This is x86 asm for u8: https://rust.godbolt.org/z/8Y8Pra. - if cmp == Ordering::Less { - left = mid + 1; - } else if cmp == Ordering::Greater { - right = mid; - } else { - return slot_hashes_data[(offset + 8)..][..32].try_into().unwrap(); - } - - size = right - left; - } - - generate_fake_slot_hash(value) } -#[must_use] -pub fn generate_fake_slot_hash(slot: Slot) -> [u8; 32] { - let slot_bytes: [u8; 8] = slot.to_be_bytes(); - let mut initial = 0; - for b in slot_bytes { - if b != 0 { - break; - } - initial += 1; - } - let slot_slice = &slot_bytes[initial..]; - let slot_slice_len = slot_slice.len(); - let mut hash = [255; 32]; - hash[32 - slot_slice_len - 1] = 0; - hash[(32 - slot_slice_len)..].copy_from_slice(slot_slice); - hash +#[maybe_async(?Send)] +pub trait SyncedAccountStorage: AccountStorage { + async fn set_code(&mut self, address: Address, chain_id: u64, code: Vector) -> Result<()>; + async fn set_storage(&mut self, address: Address, index: U256, value: [u8; 32]) -> Result<()>; + async fn increment_nonce(&mut self, address: Address, chain_id: u64) -> Result<()>; + async fn transfer( + &mut self, + from_address: Address, + to_address: Address, + chain_id: u64, + value: U256, + ) -> Result<()>; + async fn burn(&mut self, address: Address, chain_id: u64, value: U256) -> Result<()>; + async fn execute_external_instruction( + &mut self, + instruction: Instruction, + seeds: Vector>>, + fee: u64, + emulated_internally: bool, + ) -> Result<()>; + + fn snapshot(&mut self); + fn revert_snapshot(&mut self); + fn commit_snapshot(&mut self); } -#[test] -fn test_generate_fake_slot_hash() { - let slot = 0x46; - let mut expected: [u8; 32] = [255; 32]; - expected[30] = 0; - expected[31] = 0x46; - assert_eq!(generate_fake_slot_hash(slot), expected); - - let slot = 0x3e8; - let mut expected: [u8; 32] = [255; 32]; - expected[29] = 0; - expected[30] = 0x03; - expected[31] = 0xe8; - assert_eq!(generate_fake_slot_hash(slot), expected); +pub trait LogCollector { + fn collect_log( + &mut self, + address: &[u8; 20], + topics: [[u8; 32]; N], + data: &[u8], + ); } diff --git a/evm_loader/program/src/account_storage/synced.rs b/evm_loader/program/src/account_storage/synced.rs new file mode 100644 index 000000000..d80d9b08a --- /dev/null +++ b/evm_loader/program/src/account_storage/synced.rs @@ -0,0 +1,165 @@ +use ethnum::U256; +use solana_program::instruction::Instruction; +use solana_program::program::{invoke_signed_unchecked, invoke_unchecked}; +use solana_program::system_program; + +use crate::account::{AllocateResult, ContractAccount, StorageCell}; +use crate::account_storage::{SyncedAccountStorage, FAKE_OPERATOR}; +use crate::config::{ACCOUNT_SEED_VERSION, STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT}; +use crate::error::Result; +use crate::types::{vector::Vector, Address}; + +use super::{AccountStorage, ProgramAccountStorage}; + +impl<'a> SyncedAccountStorage for crate::account_storage::ProgramAccountStorage<'a> { + fn set_code(&mut self, address: Address, chain_id: u64, code: Vector) -> Result<()> { + let result = ContractAccount::allocate( + address, + &code, + &self.rent, + &self.accounts, + Some(&self.keys), + )?; + + if result != AllocateResult::Ready { + return Err(crate::error::Error::AccountSpaceAllocationFailure); + } + + ContractAccount::create( + address, + chain_id, + 0, + &code, + &self.accounts, + Some(&self.keys), + )?; + + Ok(()) + } + + fn set_storage(&mut self, address: Address, index: U256, value: [u8; 32]) -> Result<()> { + const STATIC_STORAGE_LIMIT: U256 = U256::new(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u128); + + if index < STATIC_STORAGE_LIMIT { + // Static Storage - Write into contract account + let mut contract = self.contract_account(address)?; + let index: usize = index.as_usize(); + contract.set_storage_value(index, &value); + + // Mark contract as modified + // We can't increase the revision here because it might break the pointer to the contract code inside the evm. + // TODO: After Account HEAP experiment, may be we could remove the Buffer magic + self.synced_modified_contracts.insert(*contract.pubkey()); + } else { + // Infinite Storage - Write into separate account + let cell_address = self.keys.storage_cell_address(&crate::ID, address, index); + let account = self.accounts.get(cell_address.pubkey()); + if system_program::check_id(account.owner) { + let (_, bump) = self.keys.contract_with_bump_seed(&crate::ID, address); + let sign: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], address.as_bytes(), &[bump]]; + + let mut storage = + StorageCell::create(cell_address, 1, &self.accounts, sign, &self.rent)?; + let mut cells = storage.cells_mut(); + + assert_eq!(cells.len(), 1); + cells[0].subindex = (index & 0xFF).as_u8(); + cells[0].value = value; + } else { + let mut storage = StorageCell::from_account(&crate::ID, account.clone())?; + storage.update((index & 0xFF).as_u8(), &value)?; + + storage.sync_lamports(&self.rent, &self.accounts)?; + storage.increment_revision(&self.rent, &self.accounts)?; + }; + } + + Ok(()) + } + + fn increment_nonce(&mut self, address: Address, chain_id: u64) -> Result<()> { + let mut account = self.create_balance_account(address, chain_id)?; + account.increment_nonce() + } + + fn transfer( + &mut self, + source: Address, + target: Address, + chain_id: u64, + value: U256, + ) -> Result<()> { + let mut source = self.balance_account(source, chain_id)?; + let mut target = self.create_balance_account(target, chain_id)?; + source.transfer(&mut target, value) + } + + fn burn(&mut self, address: Address, chain_id: u64, value: U256) -> Result<()> { + let mut account = self.balance_account(address, chain_id)?; + account.burn(value) + } + + fn execute_external_instruction( + &mut self, + mut instruction: Instruction, + seeds: Vector>>, + _fee: u64, + _emulated_internally: bool, + ) -> Result<()> { + let seeds = seeds + .iter() + .map(|s| s.iter().map(|s| s.as_slice()).collect::>()) + .collect::>(); + let seeds = seeds.iter().map(|s| s.as_slice()).collect::>(); + + let mut accounts_info = Vec::with_capacity(instruction.accounts.len() + 1); + + let program = self.accounts.get(&instruction.program_id).clone(); + accounts_info.push(program); + + for meta in &mut instruction.accounts { + if meta.pubkey == FAKE_OPERATOR { + meta.pubkey = self.accounts.operator_key(); + } + let account = self.accounts.get(&meta.pubkey).clone(); + accounts_info.push(account); + } + + let instruction = Instruction { + program_id: instruction.program_id, + accounts: instruction.accounts, + data: instruction.data, + }; + + if !seeds.is_empty() { + invoke_signed_unchecked(&instruction, &accounts_info, &seeds)?; + } else { + invoke_unchecked(&instruction, &accounts_info)?; + } + + Ok(()) + } + + fn snapshot(&mut self) {} + + fn revert_snapshot(&mut self) { + panic!("revert snapshot not implemented for ProgramAccountStorage"); + } + + fn commit_snapshot(&mut self) {} +} + +impl<'a> ProgramAccountStorage<'a> { + pub fn increment_revision_for_modified_contracts(&mut self) -> Result<()> { + for pubkey in self.synced_modified_contracts.iter() { + let account = self.accounts.get(pubkey); + + let mut contract = ContractAccount::from_account(&self.program_id(), account.clone())?; + contract.increment_revision(&self.rent, &self.accounts)?; + } + + self.synced_modified_contracts.clear(); + + Ok(()) + } +} diff --git a/evm_loader/program/src/allocator.rs b/evm_loader/program/src/allocator.rs deleted file mode 100644 index a67bfa847..000000000 --- a/evm_loader/program/src/allocator.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::{ - alloc::Layout, - mem::{align_of, size_of}, - ptr::NonNull, -}; - -use linked_list_allocator::Heap; -use solana_program::entrypoint::HEAP_START_ADDRESS; -use static_assertions::{const_assert, const_assert_eq}; - -const HEAP_SIZE: usize = 256 * 1024; - -#[allow(clippy::cast_possible_truncation)] // HEAP_START_ADDRESS < usize::max -const EVM_HEAP_START_ADDRESS: usize = HEAP_START_ADDRESS as usize; -const EVM_HEAP_SIZE: usize = HEAP_SIZE; - -const_assert!(HEAP_START_ADDRESS < (usize::MAX as u64)); -const_assert_eq!(EVM_HEAP_START_ADDRESS % align_of::(), 0); - -#[inline] -unsafe fn heap() -> &'static mut Heap { - // This is legal since all-zero is a valid `Heap`-struct representation - const HEAP_PTR: *mut Heap = EVM_HEAP_START_ADDRESS as *mut Heap; - let heap = &mut *HEAP_PTR; - - if heap.bottom().is_null() { - let start = (EVM_HEAP_START_ADDRESS + size_of::()) as *mut u8; - let size = EVM_HEAP_SIZE - size_of::(); - heap.init(start, size); - } - - heap -} - -pub struct SolanaAllocator; - -unsafe impl std::alloc::GlobalAlloc for SolanaAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - #[allow(clippy::option_if_let_else)] - if let Ok(non_null) = heap().allocate_first_fit(layout) { - non_null.as_ptr() - } else { - solana_program::log::sol_log("EVM Allocator out of memory"); - std::ptr::null_mut() - } - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - heap().deallocate(NonNull::new_unchecked(ptr), layout); - } - - unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { - let ptr = self.alloc(layout); - - if !ptr.is_null() { - #[cfg(target_os = "solana")] - solana_program::syscalls::sol_memset_(ptr, 0, layout.size() as u64); - #[cfg(not(target_os = "solana"))] - std::ptr::write_bytes(ptr, 0, layout.size()); - } - - ptr - } - - unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { - let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); - let new_ptr = self.alloc(new_layout); - - if !new_ptr.is_null() { - let copy_bytes = std::cmp::min(layout.size(), new_size); - - #[cfg(target_os = "solana")] - solana_program::syscalls::sol_memcpy_(new_ptr, ptr, copy_bytes as u64); - #[cfg(not(target_os = "solana"))] - std::ptr::copy_nonoverlapping(ptr, new_ptr, copy_bytes); - - self.dealloc(ptr, layout); - } - - new_ptr - } -} - -cfg_if::cfg_if! { - if #[cfg(target_os = "solana")] { - #[global_allocator] - static mut DEFAULT: SolanaAllocator = SolanaAllocator; - pub static mut EVM: SolanaAllocator = SolanaAllocator; - } else { - use std::alloc::System; - - #[global_allocator] - static mut DEFAULT: System = System; - pub static mut EVM: System = System; - } -} diff --git a/evm_loader/program/src/allocator/mod.rs b/evm_loader/program/src/allocator/mod.rs new file mode 100644 index 000000000..146628c3f --- /dev/null +++ b/evm_loader/program/src/allocator/mod.rs @@ -0,0 +1,57 @@ +#[cfg(not(target_os = "solana"))] +use std::alloc::System; +use std::mem::size_of; + +use solana_program::pubkey::Pubkey; + +#[cfg(target_os = "solana")] +use solana::solana_allocator::SolanaAllocator; +#[cfg(target_os = "solana")] +use solana::state_account_allocator::AccountAllocator; + +#[cfg(target_os = "solana")] +mod solana; + +// Holder account heap constants. + +/// See [`solana_program::entrypoint::deserialize`] for more details. +const FIRST_ACCOUNT_DATA_OFFSET: usize = + /* number of accounts */ + size_of::() + + /* duplication marker */ size_of::() + + /* is signer? */ size_of::() + + /* is writable? */ size_of::() + + /* is executable? */ size_of::() + + /* original_data_len */ size_of::() + + /* key */ size_of::() + + /* owner */ size_of::() + + /* lamports */ size_of::() + + /* factual_data_len */ size_of::(); + +/// See for more details. +const PROGRAM_DATA_INPUT_PARAMETERS_OFFSET: usize = 0x0004_0000_0000_usize; + +pub const STATE_ACCOUNT_DATA_ADDRESS: usize = + PROGRAM_DATA_INPUT_PARAMETERS_OFFSET + FIRST_ACCOUNT_DATA_OFFSET; + +#[cfg(target_os = "solana")] +pub type StateAccountAllocator = AccountAllocator; + +#[cfg(target_os = "solana")] +#[inline] +pub fn acc_allocator() -> StateAccountAllocator { + AccountAllocator +} + +#[cfg(not(target_os = "solana"))] +pub type StateAccountAllocator = System; + +#[cfg(not(target_os = "solana"))] +#[inline] +pub fn acc_allocator() -> StateAccountAllocator { + System +} + +#[cfg(target_os = "solana")] +#[global_allocator] +static mut DEFAULT: SolanaAllocator = SolanaAllocator; diff --git a/evm_loader/program/src/allocator/solana/mod.rs b/evm_loader/program/src/allocator/solana/mod.rs new file mode 100644 index 000000000..199678af9 --- /dev/null +++ b/evm_loader/program/src/allocator/solana/mod.rs @@ -0,0 +1,72 @@ +use linked_list_allocator::Heap; +use std::alloc::Layout; +use std::ptr::NonNull; + +use solana_allocator::SolanaAllocator; +use state_account_allocator::AccountAllocator; + +pub mod solana_allocator; +pub mod state_account_allocator; + +trait Alloc { + fn heap() -> &'static mut Heap; + + fn alloc_impl(layout: Layout) -> Result, ()> { + Self::heap().allocate_first_fit(layout) + } + + fn dealloc_impl(ptr: *mut u8, layout: Layout) { + unsafe { + Self::heap().deallocate(NonNull::new_unchecked(ptr), layout); + } + } +} + +macro_rules! impl_global_alloc { + ($t:ty, $err:expr) => { + unsafe impl std::alloc::GlobalAlloc for $t { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + #[allow(clippy::option_if_let_else)] + if let Ok(non_null) = Self::alloc_impl(layout) { + non_null.as_ptr() + } else { + solana_program::log::sol_log($err); + std::ptr::null_mut() + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + Self::dealloc_impl(ptr, layout); + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + let ptr = self.alloc(layout); + + if !ptr.is_null() { + solana_program::syscalls::sol_memset_(ptr, 0, layout.size() as u64); + } + + ptr + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); + let new_ptr = self.alloc(new_layout); + + if !new_ptr.is_null() { + let copy_bytes = std::cmp::min(layout.size(), new_size); + + solana_program::syscalls::sol_memcpy_(new_ptr, ptr, copy_bytes as u64); + + self.dealloc(ptr, layout); + } + + new_ptr + } + } + }; +} + +impl_global_alloc!(SolanaAllocator, "Solana Allocator out of memory"); + +impl_global_alloc!(AccountAllocator, "EVM Account Allocator out of memory"); diff --git a/evm_loader/program/src/allocator/solana/solana_allocator.rs b/evm_loader/program/src/allocator/solana/solana_allocator.rs new file mode 100644 index 000000000..b2fcbf0bc --- /dev/null +++ b/evm_loader/program/src/allocator/solana/solana_allocator.rs @@ -0,0 +1,43 @@ +use std::mem::{align_of, size_of}; + +use linked_list_allocator::Heap; +use solana_program::entrypoint::HEAP_START_ADDRESS; +use static_assertions::{const_assert, const_assert_eq}; + +use crate::allocator::solana::Alloc; + +// Solana heap constants. +#[allow(clippy::cast_possible_truncation)] // HEAP_START_ADDRESS < usize::max +const SOLANA_HEAP_START_ADDRESS: usize = HEAP_START_ADDRESS as usize; + +cfg_if::cfg_if! { + if #[cfg(feature = "rollup")] { + // NeonEVM under rollup is intended to be deployed with a forked version of Solana that supports such bigger heap. + const SOLANA_HEAP_SIZE: usize = 1024 * 1024; + } else { + const SOLANA_HEAP_SIZE: usize = 256 * 1024; + } +} + +const_assert!(HEAP_START_ADDRESS < (usize::MAX as u64)); + +const_assert_eq!(SOLANA_HEAP_START_ADDRESS % align_of::(), 0); + +#[derive(Clone, Copy)] +pub struct SolanaAllocator; + +impl Alloc for SolanaAllocator { + fn heap() -> &'static mut Heap { + // This is legal since all-zero is a valid `Heap`-struct representation + const HEAP_PTR: *mut Heap = SOLANA_HEAP_START_ADDRESS as *mut Heap; + let heap = unsafe { &mut *HEAP_PTR }; + + if heap.bottom().is_null() { + let start = (SOLANA_HEAP_START_ADDRESS + size_of::()) as *mut u8; + let size = SOLANA_HEAP_SIZE - size_of::(); + unsafe { heap.init(start, size) }; + } + + heap + } +} diff --git a/evm_loader/program/src/allocator/solana/state_account_allocator.rs b/evm_loader/program/src/allocator/solana/state_account_allocator.rs new file mode 100644 index 000000000..cc356aa58 --- /dev/null +++ b/evm_loader/program/src/allocator/solana/state_account_allocator.rs @@ -0,0 +1,46 @@ +use std::alloc::Layout; +use std::ptr::NonNull; +use std::slice; + +use linked_list_allocator::Heap; + +use crate::allocator::solana::Alloc; +use crate::allocator::STATE_ACCOUNT_DATA_ADDRESS; + +#[derive(Clone, Copy)] +pub struct AccountAllocator; + +// Configure State/Holder Account heap: the offset of the heap object is at HEAP_OBJECT_OFFSET_PTR address. +#[allow(clippy::cast_possible_truncation)] +const HEAP_OBJECT_OFFSET_PTR: usize = STATE_ACCOUNT_DATA_ADDRESS + crate::account::HEAP_OFFSET_PTR; + +impl Alloc for AccountAllocator { + fn heap() -> &'static mut Heap { + let heap_object_offset_ptr = HEAP_OBJECT_OFFSET_PTR as *const usize; + let heap_object_offset = unsafe { std::ptr::read_unaligned(heap_object_offset_ptr) }; + let heap_ptr: *mut Heap = (STATE_ACCOUNT_DATA_ADDRESS + heap_object_offset) as *mut Heap; + let heap = unsafe { &mut *heap_ptr }; + // Unlike SolanaAllocator, AccountAllocator do not init account heap here. + // It's account's responsibility to initialize it itself (likely during + // Holder/StateAccount creation), because account knows its size and thus can + // correctly specify heap size. + + heap + } +} + +unsafe impl allocator_api2::alloc::Allocator for AccountAllocator { + fn allocate(&self, layout: Layout) -> Result, allocator_api2::alloc::AllocError> { + unsafe { + Self::alloc_impl(layout) + .map(|ptr| { + NonNull::new_unchecked(slice::from_raw_parts_mut(ptr.as_ptr(), layout.size())) + }) + .map_err(|()| allocator_api2::alloc::AllocError) + } + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + Self::dealloc_impl(ptr.as_ptr(), layout); + } +} diff --git a/evm_loader/program/src/config.rs b/evm_loader/program/src/config.rs index f233fe3bd..16bd44256 100644 --- a/evm_loader/program/src/config.rs +++ b/evm_loader/program/src/config.rs @@ -2,12 +2,7 @@ #![allow(clippy::useless_transmute)] use cfg_if::cfg_if; -use const_format::formatcp; -use evm_loader_macro::{ - common_config_parser, declare_param_id, elf_config_parser, neon_elf_param, - net_specific_config_parser, operators_whitelist, -}; -use static_assertions::const_assert; +use evm_loader_macro::{common_config_parser, neon_elf_param, net_specific_config_parser}; cfg_if! { if #[cfg(feature = "mainnet")] { @@ -18,21 +13,23 @@ cfg_if! { net_specific_config_parser!("config/devnet.toml"); } else if #[cfg(feature = "govertest")] { net_specific_config_parser!("config/govertest.toml"); - } else { + } else if #[cfg(feature = "rollup")] { + net_specific_config_parser!("config/rollup.toml"); + } + else { net_specific_config_parser!("config/default.toml"); } } -common_config_parser!("config/common.toml"); - cfg_if! { if #[cfg(feature = "emergency")] { - neon_elf_param!( NEON_STATUS_NAME, "EMERGENCY"); + neon_elf_param!(NEON_STATUS_NAME, "EMERGENCY"); } else { - neon_elf_param!( NEON_STATUS_NAME, "WORK"); + neon_elf_param!(NEON_STATUS_NAME, "WORK"); } } -elf_config_parser!("config/elf_params.toml"); +common_config_parser!("config/common.toml"); -const_assert!(token_mint::decimals() <= 18); +neon_elf_param!(NEON_PKG_VERSION, env!("CARGO_PKG_VERSION")); +neon_elf_param!(NEON_REVISION, env!("NEON_REVISION")); diff --git a/evm_loader/program/src/debug.rs b/evm_loader/program/src/debug.rs index a23d4429d..e7fe5153f 100644 --- a/evm_loader/program/src/debug.rs +++ b/evm_loader/program/src/debug.rs @@ -15,3 +15,34 @@ macro_rules! debug_print { macro_rules! debug_print { ($( $args:expr ),*) => {}; } + +#[cfg(target_os = "solana")] +macro_rules! log_msg { + ($($arg:tt)*) => (solana_program::msg!($($arg)*)); +} + +#[cfg(not(target_os = "solana"))] +macro_rules! log_msg { + ($($arg:tt)*) => (log::info!($($arg)*)); +} + +#[inline] +pub fn log_data(data: &[&[u8]]) { + #[cfg(target_os = "solana")] + solana_program::log::sol_log_data(data); + + #[cfg(not(target_os = "solana"))] + { + let mut messages: Vec = Vec::new(); + + for f in data { + if let Ok(str) = String::from_utf8(f.to_vec()) { + messages.push(str); + } else { + messages.push(hex::encode(f)); + } + } + + log::info!("Program Data: {}", messages.join(" ")); + } +} diff --git a/evm_loader/program/src/entrypoint.rs b/evm_loader/program/src/entrypoint.rs index f41a95a87..eb075a85a 100644 --- a/evm_loader/program/src/entrypoint.rs +++ b/evm_loader/program/src/entrypoint.rs @@ -7,18 +7,53 @@ use solana_program::{ pubkey::Pubkey, }; -#[cfg(not(feature = "emergency"))] use crate::{instruction, instruction::EvmInstruction}; entrypoint!(process_instruction); #[cfg(feature = "emergency")] fn process_instruction<'a>( - _program_id: &'a Pubkey, - _accounts: &'a [AccountInfo<'a>], - _instruction_data: &[u8], + program_id: &'a Pubkey, + accounts: &'a [AccountInfo<'a>], + instruction_data: &[u8], ) -> ProgramResult { - Err!(ProgramError::InvalidInstructionData; "Emergency image: all instructions are rejected") + assert!(crate::check_id(program_id)); + + let (tag, instruction) = instruction_data + .split_first() + .ok_or(ProgramError::InvalidInstructionData)?; + + match EvmInstruction::parse(tag)? { + EvmInstruction::ConfigGetChainCount => { + instruction::config_get_chain_count::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetChainInfo => { + instruction::config_get_chain_info::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetEnvironment => { + instruction::config_get_environment::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetPropertyCount => { + instruction::config_get_property_count::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetPropertyByIndex => { + instruction::config_get_property_by_index::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetPropertyByName => { + instruction::config_get_property_by_name::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetStatus => { + instruction::config_get_status::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetVersion => { + instruction::config_get_version::process(program_id, accounts, instruction) + } + _ => { + log_msg!("Emergency image: all instructions are rejected"); + Err(ProgramError::InvalidInstructionData.into()) + } + } + .map_err(ProgramError::from) } #[cfg(not(feature = "emergency"))] @@ -27,24 +62,25 @@ fn process_instruction<'a>( accounts: &'a [AccountInfo<'a>], instruction_data: &[u8], ) -> ProgramResult { - let (tag, instruction) = instruction_data.split_first().ok_or_else( - || E!(ProgramError::InvalidInstructionData; "Invalid instruction - {:?}", instruction_data), - )?; + use crate::error::Error; + + assert!(crate::check_id(program_id)); + + let (tag, instruction) = instruction_data + .split_first() + .ok_or(ProgramError::InvalidInstructionData)?; match EvmInstruction::parse(tag)? { EvmInstruction::HolderCreate => { instruction::account_holder_create::process(program_id, accounts, instruction) - .map_err(ProgramError::from) } EvmInstruction::HolderDelete => { instruction::account_holder_delete::process(program_id, accounts, instruction) - .map_err(ProgramError::from) } EvmInstruction::HolderWrite => { instruction::account_holder_write::process(program_id, accounts, instruction) - .map_err(ProgramError::from) } - EvmInstruction::DepositV03 => { + EvmInstruction::Deposit => { instruction::neon_tokens_deposit::process(program_id, accounts, instruction) } EvmInstruction::Cancel => { @@ -56,7 +92,6 @@ fn process_instruction<'a>( accounts, instruction, ) - .map_err(ProgramError::from) } EvmInstruction::TransactionExecuteFromAccount => { instruction::transaction_execute_from_account::process( @@ -64,7 +99,6 @@ fn process_instruction<'a>( accounts, instruction, ) - .map_err(ProgramError::from) } EvmInstruction::TransactionStepFromInstruction => { instruction::transaction_step_from_instruction::process( @@ -72,11 +106,9 @@ fn process_instruction<'a>( accounts, instruction, ) - .map_err(ProgramError::from) } EvmInstruction::TransactionStepFromAccount => { instruction::transaction_step_from_account::process(program_id, accounts, instruction) - .map_err(ProgramError::from) } EvmInstruction::TransactionStepFromAccountNoChainId => { instruction::transaction_step_from_account_no_chainid::process( @@ -84,24 +116,65 @@ fn process_instruction<'a>( accounts, instruction, ) - .map_err(ProgramError::from) - } - EvmInstruction::CreateAccountV03 => { - instruction::account_create::process(program_id, accounts, instruction) } EvmInstruction::CollectTreasure => { instruction::collect_treasury::process(program_id, accounts, instruction) + .map_err(Error::from) } EvmInstruction::CreateMainTreasury => { instruction::create_main_treasury::process(program_id, accounts, instruction) + .map_err(Error::from) + } + EvmInstruction::AccountCreateBalance => { + instruction::account_create_balance::process(program_id, accounts, instruction) + } + EvmInstruction::TransactionExecuteFromInstructionWithSolanaCall => { + instruction::transaction_execute_from_instruction_solana_call::process( + program_id, + accounts, + instruction, + ) + } + EvmInstruction::TransactionExecuteFromAccountWithSolanaCall => { + instruction::transaction_execute_from_account_solana_call::process( + program_id, + accounts, + instruction, + ) + } + EvmInstruction::ConfigGetChainCount => { + instruction::config_get_chain_count::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetChainInfo => { + instruction::config_get_chain_info::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetEnvironment => { + instruction::config_get_environment::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetPropertyCount => { + instruction::config_get_property_count::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetPropertyByIndex => { + instruction::config_get_property_by_index::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetPropertyByName => { + instruction::config_get_property_by_name::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetStatus => { + instruction::config_get_status::process(program_id, accounts, instruction) + } + EvmInstruction::ConfigGetVersion => { + instruction::config_get_version::process(program_id, accounts, instruction) + } + EvmInstruction::OperatorBalanceCreate => { + instruction::operator_create_balance::process(program_id, accounts, instruction) } - EvmInstruction::AccountBlockAdd => { - instruction::account_block_add::process(program_id, accounts, instruction) - .map_err(ProgramError::from) + EvmInstruction::OperatorBalanceDelete => { + instruction::operator_delete_balance::process(program_id, accounts, instruction) } - EvmInstruction::TestAccountUpdateNonce => { - instruction::test_account_update_nonce::process(program_id, accounts, instruction) - .map_err(ProgramError::from) + EvmInstruction::OperatorBalanceWithdraw => { + instruction::operator_withdraw_balance::process(program_id, accounts, instruction) } } + .map_err(ProgramError::from) } diff --git a/evm_loader/program/src/error.rs b/evm_loader/program/src/error.rs index 40631225e..2e2cd61f0 100644 --- a/evm_loader/program/src/error.rs +++ b/evm_loader/program/src/error.rs @@ -1,15 +1,18 @@ //! Error types #![allow(clippy::use_self)] -use std::{array::TryFromSliceError, num::TryFromIntError}; +use std::{array::TryFromSliceError, num::TryFromIntError, str::Utf8Error}; +use crate::allocator::acc_allocator; use ethnum::U256; use solana_program::{ - program_error::ProgramError, pubkey::Pubkey, secp256k1_recover::Secp256k1RecoverError, + program_error::ProgramError, + pubkey::{Pubkey, PubkeyError}, + secp256k1_recover::Secp256k1RecoverError, }; use thiserror::Error; -use crate::types::Address; +use crate::types::{Address, Vector}; /// Errors that may be returned by the EVM Loader program. #[derive(Error, Debug)] @@ -20,6 +23,9 @@ pub enum Error { #[error("Solana Program Error: {0}")] ProgramError(#[from] ProgramError), + #[error("Solana Pubkey Error: {0}")] + PubkeyError(#[from] PubkeyError), + #[error("RLP error: {0}")] RlpError(#[from] rlp::DecoderError), @@ -29,6 +35,9 @@ pub enum Error { #[error("Bincode error: {0}")] BincodeError(#[from] bincode::Error), + #[error("IO error: {0}")] + BorshError(#[from] std::io::Error), + #[error("FromHexError error: {0}")] FromHexError(#[from] hex::FromHexError), @@ -38,11 +47,17 @@ pub enum Error { #[error("TryFromSliceError error: {0}")] TryFromSliceError(#[from] TryFromSliceError), + #[error("Utf8Error error: {0}")] + Utf8Error(#[from] Utf8Error), + #[error("Account {0} - not found")] - AccountMissing(Address), + AccountMissing(Pubkey), - #[error("Account {0} - blocked")] - AccountBlocked(Address), + #[error("Account {0} - blocked, trying to execute transaction on rw locked account")] + AccountBlocked(Pubkey), + + #[error("Account {0} - was empty, created by another transaction")] + AccountCreatedByAnotherTransaction(Pubkey), #[error("Account {0} - invalid tag, expected {1}")] AccountInvalidTag(Pubkey, u8), @@ -68,24 +83,36 @@ pub enum Error { #[error("Account {0} - already initialized")] AccountAlreadyInitialized(Pubkey), + #[error("Account {0} - in legacy format")] + AccountLegacy(Pubkey), + #[error("Operator is not authorized")] UnauthorizedOperator, #[error("Storage Account is uninitialized")] StorageAccountUninitialized, - #[error("Storage Account is finalized")] + #[error("Transaction already finalized")] StorageAccountFinalized, #[error("Unknown extension method selector {1:?}, contract {0}")] UnknownPrecompileMethodSelector(Address, [u8; 4]), - #[error("Insufficient balance for transfer, account = {0}, required = {1}")] - InsufficientBalance(Address, U256), + #[error("Insufficient balance for transfer, account = {0}, chain = {1}, required = {2}")] + InsufficientBalance(Address, u64, U256), + + #[error("Invalid token for transfer, account = {0}, chain = {1}")] + InvalidTransferToken(Address, u64), #[error("Out of Gas, limit = {0}, required = {1}")] OutOfGas(U256, U256), + #[error("Out of Priority Fee, limit = {0}, required = {1}")] + OutOfPriorityFee(U256, U256), + + #[error("Invalid gas balance account")] + GasReceiverInvalidChainId, + #[error("EVM Stack Overflow")] StackOverflow, @@ -113,14 +140,14 @@ pub enum Error { #[error("EVM encountered unknown opcode, contract = {0}, opcode = {1:X}")] UnknownOpcode(Address, u8), - #[error("Account {0} nonce overflow")] + #[error("Account {0} - nonce overflow")] NonceOverflow(Address), #[error("Invalid Nonce, origin {0} nonce {1} != Transaction nonce {2}")] InvalidTransactionNonce(Address, u64, u64), #[error("Invalid Chain ID {0}")] - InvalidChainId(U256), + InvalidChainId(u64), #[error("Attempt to deploy to existing account {0}, caller = {1}")] DeployToExistingAccount(Address, Address), @@ -143,15 +170,60 @@ pub enum Error { #[error("Holder Account - invalid owner {0}, expected = {1}")] HolderInvalidOwner(Pubkey, Pubkey), + #[error("Holder Account - insufficient size {0}, required = {1}")] + HolderInsufficientSize(usize, usize), + #[error("Holder Account - invalid transaction hash {}, expected = {}", hex::encode(.0), hex::encode(.1))] HolderInvalidHash([u8; 32], [u8; 32]), + + #[error( + "Deployment of contract which needs more than 10kb of account space needs several \ + transactions for reallocation and cannot be performed in a single instruction. \ + That's why you have to use iterative transaction for the deployment." + )] + AccountSpaceAllocationFailure, + + #[error("Invalid account for call {0}")] + InvalidAccountForCall(Pubkey), + + #[error("Call for external Solana programs not available in this mode")] + UnavalableExternalSolanaCall, + + #[error("Program not allowed to call itself")] + RecursiveCall, + + #[error("External call fails {0}: {1}")] + ExternalCallFailed(Pubkey, String), + + #[error("Operator Balance - invalid owner {0}, expected = {1}")] + OperatorBalanceInvalidOwner(Pubkey, Pubkey), + + #[error("Operator Balance - not found")] + OperatorBalanceMissing, + + #[error("Operator Balance - invalid chainId")] + OperatorBalanceInvalidChainId, + + #[error("Operator Balance - invalid address")] + OperatorBalanceInvalidAddress, + + #[error( + "Instructions that execute Ethereum DynamicGas transaction (EIP-1559) should specify priority fee." + )] + PriorityFeeNotSpecified, + + #[error("Error while parsing priority fee instructions: {0}")] + PriorityFeeParsingError(String), + + #[error("Priority fee calculation error: {0}")] + PriorityFeeError(String), } pub type Result = std::result::Result; impl From for ProgramError { fn from(e: Error) -> Self { - solana_program::msg!("{}", e); + log_msg!("{}", e); match e { Error::ProgramError(e) => e, _ => Self::Custom(0), @@ -165,6 +237,12 @@ impl From<&'static str> for Error { } } +impl From for Error { + fn from(value: String) -> Self { + Self::Custom(value) + } +} + /// Macro to log a `ProgramError` in the current transaction log /// with the source file position like: file.rc:42 /// and additional info if needed @@ -188,31 +266,8 @@ macro_rules! Err { }); } -/// Macro to log a `ProgramError` in the current transaction log. -/// with the source file position like: file.rc:777 -/// and additional info if needed -/// See `https://github.com/neonlabsorg/neon-evm/issues/159` -/// -/// # Examples -/// -/// ```ignore -/// # map_err(|s| E!(ProgramError::InvalidArgument; "s={:?}", s)) -/// ``` -/// -macro_rules! E { - ( $n:expr; $($args:expr),* ) => ({ - #[cfg(target_os = "solana")] - solana_program::msg!("{}:{} : {}", file!(), line!(), &format!($($args),*)); - - #[cfg(all(not(target_os = "solana"), feature = "log"))] - log::error!("{}", &format!($($args),*)); - - $n - }); -} - #[must_use] -fn format_revert_error(msg: &[u8]) -> Option<&str> { +pub fn format_revert_error(msg: &[u8]) -> Option<&str> { if msg.starts_with(&[0x08, 0xc3, 0x79, 0xa0]) { // Error(string) function selector let msg = &msg[4..]; @@ -239,7 +294,7 @@ fn format_revert_error(msg: &[u8]) -> Option<&str> { } #[must_use] -fn format_revert_panic(msg: &[u8]) -> Option { +pub fn format_revert_panic(msg: &[u8]) -> Option { if msg.starts_with(&[0x4e, 0x48, 0x7b, 0x71]) { // Panic(uint256) function selector let msg = &msg[4..]; @@ -256,22 +311,22 @@ fn format_revert_panic(msg: &[u8]) -> Option { pub fn print_revert_message(msg: &[u8]) { if msg.is_empty() { - return solana_program::msg!("Revert"); + return log_msg!("Revert"); } if let Some(reason) = format_revert_error(msg) { - return solana_program::msg!("Revert: Error(\"{}\")", reason); + return log_msg!("Revert: Error(\"{}\")", reason); } if let Some(reason) = format_revert_panic(msg) { - return solana_program::msg!("Revert: Panic({:#x})", reason); + return log_msg!("Revert: Panic({:#x})", reason); } - solana_program::msg!("Revert: {}", hex::encode(msg)); + log_msg!("Revert: {}", hex::encode(msg)); } #[must_use] -pub fn build_revert_message(msg: &str) -> Vec { +pub fn build_revert_message(msg: &str) -> Vector { let data_len = if msg.len() % 32 == 0 { std::cmp::max(msg.len(), 32) } else { @@ -279,7 +334,7 @@ pub fn build_revert_message(msg: &str) -> Vec { }; let capacity = 4 + 32 + 32 + data_len; - let mut result = Vec::with_capacity(capacity); + let mut result = Vector::with_capacity_in(capacity, acc_allocator()); result.extend_from_slice(&[0x08, 0xc3, 0x79, 0xa0]); // Error(string) function selector let offset = U256::new(0x20); diff --git a/evm_loader/program/src/evm/buffer.rs b/evm_loader/program/src/evm/buffer.rs index 580f715e5..da6ff8843 100644 --- a/evm_loader/program/src/evm/buffer.rs +++ b/evm_loader/program/src/evm/buffer.rs @@ -1,59 +1,64 @@ -use std::{ - alloc::{GlobalAlloc, Layout}, - ops::{Deref, Range}, - ptr::NonNull, -}; +use std::ops::{Deref, Range}; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; -const BUFFER_ALIGN: usize = 1; +use crate::types::vector::VectorSliceExt; +use crate::types::Vector; +use crate::vector; -#[derive(Debug)] +#[cfg_attr(test, derive(Debug, PartialEq))] +#[repr(C)] enum Inner { - Empty, - Owned { - ptr: NonNull, - len: usize, - }, + Owned(Vector), Account { - key: Pubkey, - data: *mut u8, - range: Range, - }, - AccountUninit { key: Pubkey, range: Range, + data: *const u8, }, } -#[derive(Debug)] +#[cfg_attr(test, derive(Debug))] +#[repr(C)] pub struct Buffer { + // We maintain a ptr and len to be able to construct a slice without having to discriminate + // inner. This means we should not allow mutation of inner after the construction of a buffer. ptr: *const u8, len: usize, inner: Inner, } +#[cfg(test)] +impl core::cmp::PartialEq for Buffer { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + impl Buffer { fn new(inner: Inner) -> Self { let (ptr, len) = match &inner { - Inner::Empty => (NonNull::dangling().as_ptr(), 0), - Inner::Owned { ptr, len } => (ptr.as_ptr(), *len), + Inner::Owned(data) => (data.as_ptr(), data.len()), Inner::Account { data, range, .. } => { let ptr = unsafe { data.add(range.start) }; (ptr, range.len()) } - Inner::AccountUninit { .. } => (std::ptr::null_mut(), 0), }; Buffer { ptr, len, inner } } + /// # Safety + /// + /// This function was marked as unsafe until correct lifetimes will be set. + /// At the moment, `Buffer` may outlive `account`, since no lifetimes has been set, + /// so they are not checked by the compiler and it's the user's responsibility to take + /// care of them. #[must_use] - pub fn from_account(account: &AccountInfo, range: Range) -> Self { + pub unsafe fn from_account(account: &AccountInfo, range: Range) -> Self { let data = unsafe { // todo cell_leak #69099 let ptr = account.data.as_ptr(); - (*ptr).as_mut_ptr() + (*ptr).as_ptr() }; Buffer::new(Inner::Account { @@ -64,43 +69,23 @@ impl Buffer { } #[must_use] - pub fn from_slice(v: &[u8]) -> Self { - if v.is_empty() { - return Self::empty(); - } - - unsafe { - let len = v.len(); - - let layout = Layout::from_size_align_unchecked(len, BUFFER_ALIGN); - let ptr = crate::allocator::EVM.alloc(layout); - if ptr.is_null() { - std::alloc::handle_alloc_error(layout); - } - - cfg_if::cfg_if! { - if #[cfg(target_os = "solana")] { - solana_program::syscalls::sol_memcpy_(ptr, v.as_ptr(), len as u64); - } else { - std::ptr::copy_nonoverlapping(v.as_ptr(), ptr, len); - } - } + pub fn from_vector(data: Vector) -> Self { + Self::new(Inner::Owned(data)) + } - Buffer::new(Inner::Owned { - ptr: NonNull::new_unchecked(ptr), - len, - }) - } + #[must_use] + pub fn from_slice(v: &[u8]) -> Self { + Self::from_vector(v.to_vector()) } #[must_use] pub fn empty() -> Self { - Buffer::new(Inner::Empty) + Self::from_vector(vector![]) } #[must_use] pub fn uninit_data(&self) -> Option<(Pubkey, Range)> { - if let Inner::AccountUninit { key, range } = &self.inner { + if let Inner::Account { key, range, .. } = &self.inner { Some((*key, range.clone())) } else { None @@ -120,17 +105,6 @@ impl Buffer { } } -impl Drop for Buffer { - fn drop(&mut self) { - if let Inner::Owned { ptr, len } = self.inner { - unsafe { - let layout = Layout::from_size_align_unchecked(len, BUFFER_ALIGN); - crate::allocator::EVM.dealloc(ptr.as_ptr(), layout); - } - } - } -} - impl Deref for Buffer { type Target = [u8]; @@ -146,16 +120,11 @@ impl Clone for Buffer { #[inline] fn clone(&self) -> Self { match &self.inner { - Inner::Empty => Self::empty(), Inner::Owned { .. } => Self::from_slice(self), Inner::Account { key, data, range } => Self::new(Inner::Account { - key: *key, - data: *data, - range: range.clone(), - }), - Inner::AccountUninit { key, range } => Self::new(Inner::AccountUninit { key: *key, range: range.clone(), + data: *data, }), } } @@ -167,93 +136,69 @@ impl Default for Buffer { } } -impl serde::Serialize for Buffer { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStructVariant; - - match &self.inner { - Inner::Empty => serializer.serialize_unit_variant("evm_buffer", 0, "empty"), - Inner::Owned { ptr, len } => { - let slice = unsafe { std::slice::from_raw_parts(ptr.as_ptr(), *len) }; - let bytes = serde_bytes::Bytes::new(slice); - serializer.serialize_newtype_variant("evm_buffer", 1, "owned", bytes) - } - Inner::Account { key, range, .. } => { - let mut sv = serializer.serialize_struct_variant("evm_buffer", 2, "account", 2)?; - sv.serialize_field("key", key)?; - sv.serialize_field("range", range)?; - sv.end() - } - Inner::AccountUninit { .. } => { - unreachable!() - } - } +#[cfg(test)] +mod tests { + use super::*; + use crate::{executor::OwnedAccountInfo, vector}; + use solana_program::account_info::IntoAccountInfo; + + macro_rules! assert_slice_ptr_eq { + ($actual:expr, $expected:expr) => {{ + let actual: &[_] = $actual; + let (expected_ptr, expected_len): (*const _, usize) = $expected; + assert_eq!(actual.as_ptr(), expected_ptr); + assert_eq!(actual.len(), expected_len); + }}; } -} - -impl<'de> serde::Deserialize<'de> for Buffer { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct BufferVisitor; - - impl<'de> serde::de::Visitor<'de> for BufferVisitor { - type Value = Buffer; - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("EVM Buffer") - } - - fn visit_unit(self) -> Result - where - E: serde::de::Error, - { - Ok(Buffer::empty()) - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - Ok(Buffer::from_slice(v)) - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let key = seq - .next_element()? - .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; - let range = seq - .next_element()? - .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; - Ok(Buffer::new(Inner::AccountUninit { key, range })) - } + #[test] + fn test_deref_owned_empty() { + let data = vector![]; + let expected = (data.as_ptr(), data.len()); + assert_slice_ptr_eq!(&*Buffer::from_vector(data), expected); + } - fn visit_enum(self, data: A) -> Result - where - A: serde::de::EnumAccess<'de>, - { - use serde::de::VariantAccess; + #[test] + fn test_deref_owned_non_empty() { + let data = vector![1]; + let expected = (data.as_ptr(), data.len()); + assert_slice_ptr_eq!(&*Buffer::from_vector(data), expected); + } - let (index, variant) = data.variant::()?; - match index { - 0 => variant.unit_variant().map(|_| Buffer::empty()), - 1 => variant.newtype_variant().map(Buffer::from_slice), - 2 => variant.struct_variant(&["key", "range"], self), - _ => Err(serde::de::Error::unknown_variant( - "_", - &["empty", "owned", "account"], - )), - } + impl OwnedAccountInfo { + fn with_data(data: Vector) -> Self { + OwnedAccountInfo { + key: Pubkey::default(), + lamports: 0, + data, + owner: Pubkey::default(), + rent_epoch: 0, + is_signer: false, + is_writable: false, + executable: false, } } + } + + #[test] + fn test_deref_account_empty() { + let data = vector![]; + let expected = (data.as_ptr(), data.len()); + let mut account_info = OwnedAccountInfo::with_data(data); + assert_slice_ptr_eq!( + &*unsafe { Buffer::from_account(&account_info.into_account_info(), 0..expected.1) }, + expected + ); + } - deserializer.deserialize_enum("evm_buffer", &["empty", "owned", "account"], BufferVisitor) + #[test] + fn test_deref_account_non_empty() { + let data = vector![1]; + let expected = (data.as_ptr(), data.len()); + let mut account_info = OwnedAccountInfo::with_data(data); + assert_slice_ptr_eq!( + &*unsafe { Buffer::from_account(&account_info.into_account_info(), 0..expected.1) }, + expected + ); } } diff --git a/evm_loader/program/src/evm/database.rs b/evm_loader/program/src/evm/database.rs index 11cc85f1a..8c260a397 100644 --- a/evm_loader/program/src/evm/database.rs +++ b/evm_loader/program/src/evm/database.rs @@ -1,31 +1,61 @@ use super::{Buffer, Context}; -use crate::{error::Result, types::Address}; +use crate::account_storage::LogCollector; +use crate::types::Vector; +use crate::{error::Result, executor::OwnedAccountInfo, types::Address}; use ethnum::U256; -use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; +use maybe_async::maybe_async; +use solana_program::{ + account_info::AccountInfo, instruction::Instruction, pubkey::Pubkey, rent::Rent, +}; -pub trait Database { - fn chain_id(&self) -> U256; +#[maybe_async(?Send)] +pub trait Database: LogCollector { + fn program_id(&self) -> &Pubkey; + fn operator(&self) -> Pubkey; + fn chain_id_to_token(&self, chain_id: u64) -> Pubkey; + fn contract_pubkey(&self, address: Address) -> (Pubkey, u8); - fn nonce(&self, address: &Address) -> Result; - fn increment_nonce(&mut self, address: Address) -> Result<()>; + fn default_chain_id(&self) -> u64; + fn is_valid_chain_id(&self, chain_id: u64) -> bool; + async fn contract_chain_id(&self, address: Address) -> Result; - fn balance(&self, address: &Address) -> Result; - fn transfer(&mut self, source: Address, target: Address, value: U256) -> Result<()>; + async fn nonce(&self, address: Address, chain_id: u64) -> Result; + async fn increment_nonce(&mut self, address: Address, chain_id: u64) -> Result<()>; - fn code_size(&self, address: &Address) -> Result; - fn code_hash(&self, address: &Address) -> Result<[u8; 32]>; - fn code(&self, address: &Address) -> Result; - fn set_code(&mut self, address: Address, code: Buffer) -> Result<()>; - fn selfdestruct(&mut self, address: Address) -> Result<()>; + async fn balance(&self, address: Address, chain_id: u64) -> Result; + async fn transfer( + &mut self, + source: Address, + target: Address, + chain_id: u64, + value: U256, + ) -> Result<()>; + async fn burn(&mut self, address: Address, chain_id: u64, value: U256) -> Result<()>; + + async fn code_size(&self, address: Address) -> Result; + async fn code(&self, address: Address) -> Result; + async fn set_code(&mut self, address: Address, chain_id: u64, code: Vector) -> Result<()>; - fn storage(&self, address: &Address, index: &U256) -> Result<[u8; 32]>; - fn set_storage(&mut self, address: Address, index: U256, value: [u8; 32]) -> Result<()>; + async fn storage(&self, address: Address, index: U256) -> Result<[u8; 32]>; + async fn set_storage(&mut self, address: Address, index: U256, value: [u8; 32]) -> Result<()>; + + async fn transient_storage(&self, address: Address, index: U256) -> Result<[u8; 32]>; + fn set_transient_storage( + &mut self, + address: Address, + index: U256, + value: [u8; 32], + ) -> Result<()>; - fn block_hash(&self, number: U256) -> Result<[u8; 32]>; + async fn block_hash(&self, number: U256) -> Result<[u8; 32]>; fn block_number(&self) -> Result; fn block_timestamp(&self) -> Result; + fn rent(&self) -> &Rent; + fn return_data(&self) -> Option<(Pubkey, Vec)>; + fn set_return_data(&mut self, data: &[u8]); - fn map_solana_account(&self, address: &Pubkey, action: F) -> R + async fn external_account(&self, address: Pubkey) -> Result; + async fn map_solana_account(&self, address: &Pubkey, action: F) -> R where F: FnOnce(&AccountInfo) -> R; @@ -33,11 +63,60 @@ pub trait Database { fn revert_snapshot(&mut self); fn commit_snapshot(&mut self); - fn precompile_extension( + async fn queue_external_instruction( + &mut self, + instruction: Instruction, + seeds: Vector>>, + fee: u64, + emulated_internally: bool, + ) -> Result<()>; + + async fn precompile_extension( &mut self, context: &Context, address: &Address, data: &[u8], is_static: bool, - ) -> Option>>; + ) -> Option>>; +} + +/// Provides convenience methods that can be implemented in terms of `Database`. +#[maybe_async(?Send)] +pub trait DatabaseExt { + /// Returns whether an account exists and is non-empty as specified in + /// https://eips.ethereum.org/EIPS/eip-161. + async fn account_exists(&self, address: Address, chain_id: u64) -> Result; + + /// Returns the code hash for an address as specified in + /// https://eips.ethereum.org/EIPS/eip-1052. + async fn code_hash(&self, address: Address, chain_id: u64) -> Result<[u8; 32]>; +} + +#[maybe_async(?Send)] +impl DatabaseExt for T { + async fn account_exists(&self, address: Address, chain_id: u64) -> Result { + Ok(self.nonce(address, chain_id).await? > 0 || self.balance(address, chain_id).await? > 0) + } + + async fn code_hash(&self, address: Address, chain_id: u64) -> Result<[u8; 32]> { + // The function `Database::code` returns a zero-length buffer if the account exists with + // zero-length code, but also when the account does not exist. This makes it necessary to + // also check if the account exists when the returned buffer is empty. + // + // We could simplify the implementation by checking if the account exists first, but that + // would lead to more computation in what we think is the common case where the account + // exists and contains code. + let code = self.code(address).await?; + let bytes_to_hash: Option<&[u8]> = if !code.is_empty() { + Some(&*code) + } else if self.account_exists(address, chain_id).await? { + Some(&[]) + } else { + None + }; + + Ok(bytes_to_hash.map_or([0; 32], |bytes| { + solana_program::keccak::hash(bytes).to_bytes() + })) + } } diff --git a/evm_loader/program/src/evm/memory.rs b/evm_loader/program/src/evm/memory.rs index acbdd6270..514f0df3e 100644 --- a/evm_loader/program/src/evm/memory.rs +++ b/evm_loader/program/src/evm/memory.rs @@ -1,17 +1,21 @@ -use solana_program::program_memory::{sol_memcpy, sol_memset}; use std::alloc::{GlobalAlloc, Layout}; use std::ops::Range; -use super::utils::checked_next_multiple_of_32; -use super::{tracing_event, Buffer}; +use crate::allocator::acc_allocator; +use solana_program::program_memory::{sol_memcpy, sol_memmove, sol_memset}; + use crate::error::Error; +use super::utils::checked_next_multiple_of_32; +use super::Buffer; + const MAX_MEMORY_SIZE: usize = 64 * 1024; const MEMORY_CAPACITY: usize = 1024; const MEMORY_ALIGN: usize = 1; static_assertions::const_assert!(MEMORY_ALIGN.is_power_of_two()); +#[repr(C)] pub struct Memory { data: *mut u8, capacity: usize, @@ -26,7 +30,7 @@ impl Memory { pub fn with_capacity(capacity: usize) -> Self { unsafe { let layout = Layout::from_size_align_unchecked(capacity, MEMORY_ALIGN); - let data = crate::allocator::EVM.alloc_zeroed(layout); + let data = acc_allocator().alloc_zeroed(layout); if data.is_null() { std::alloc::handle_alloc_error(layout); } @@ -39,27 +43,7 @@ impl Memory { } } - pub fn from_buffer(v: &[u8]) -> Self { - let capacity = v.len().next_power_of_two().max(MEMORY_CAPACITY); - - unsafe { - let layout = Layout::from_size_align_unchecked(capacity, MEMORY_ALIGN); - let data = crate::allocator::EVM.alloc_zeroed(layout); - if data.is_null() { - std::alloc::handle_alloc_error(layout); - } - - std::ptr::copy_nonoverlapping(v.as_ptr(), data, v.len()); - - Self { - data, - capacity, - size: v.len(), - } - } - } - - #[allow(dead_code)] + #[cfg(not(target_os = "solana"))] pub fn to_vec(&self) -> Vec { let slice = unsafe { std::slice::from_raw_parts(self.data, self.size) }; slice.to_vec() @@ -91,7 +75,7 @@ impl Memory { unsafe { let old_layout = Layout::from_size_align_unchecked(self.capacity, MEMORY_ALIGN); - let new_data = crate::allocator::EVM.realloc(self.data, old_layout, new_capacity); + let new_data = acc_allocator().realloc(self.data, old_layout, new_capacity); if new_data.is_null() { let layout = Layout::from_size_align_unchecked(new_capacity, MEMORY_ALIGN); std::alloc::handle_alloc_error(layout); @@ -140,11 +124,6 @@ impl Memory { } pub fn write_32(&mut self, offset: usize, value: &[u8; 32]) -> Result<(), Error> { - tracing_event!(super::tracing::Event::MemorySet { - offset, - data: value.to_vec() - }); - self.realloc(offset, 32)?; unsafe { @@ -156,11 +135,6 @@ impl Memory { } pub fn write_byte(&mut self, offset: usize, value: u8) -> Result<(), Error> { - tracing_event!(super::tracing::Event::MemorySet { - offset, - data: vec![value] - }); - self.realloc(offset, 1)?; unsafe { @@ -191,36 +165,16 @@ impl Memory { match source_offset { source_offset if source_offset >= source.len() => { - tracing_event!(super::tracing::Event::MemorySet { - offset, - data: vec![0; length] - }); - sol_memset(data, 0, length); } source_offset if (source_offset + length) > source.len() => { let source = &source[source_offset..]; - tracing_event!(super::tracing::Event::MemorySet { - offset, - data: { - let mut buffer = vec![0_u8; length]; - buffer[..source.len()].copy_from_slice(source); - buffer - } - }); - data[..source.len()].copy_from_slice(source); data[source.len()..].fill(0_u8); } source_offset => { let source = &source[source_offset..source_offset + length]; - - tracing_event!(super::tracing::Event::MemorySet { - offset, - data: source.to_vec() - }); - sol_memcpy(data, source, length); } } @@ -238,53 +192,37 @@ impl Memory { let slice = self.read(offset, length)?; Ok(Buffer::from_slice(slice)) } -} -impl Drop for Memory { - fn drop(&mut self) { - unsafe { - let layout = Layout::from_size_align_unchecked(self.capacity, MEMORY_ALIGN); - crate::allocator::EVM.dealloc(self.data, layout); + pub fn copy_within( + &mut self, + source: usize, + destination: usize, + length: usize, + ) -> Result<(), Error> { + if length == 0_usize { + return Ok(()); } - } -} - -impl serde::Serialize for Memory { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let data = unsafe { std::slice::from_raw_parts(self.data, self.capacity) }; - serializer.serialize_bytes(&data[..self.size()]) - } -} -impl<'de> serde::Deserialize<'de> for Memory { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct BytesVisitor; + // If length > 0 and (src + length or dst + length) is beyond the current memory length, the memory is extended + self.realloc(std::cmp::max(source, destination), length)?; - impl<'de> serde::de::Visitor<'de> for BytesVisitor { - type Value = Memory; + // SAFETY: self.realloc ensures that the memory is large enough to perform the memmove without out of bounds access + unsafe { + let src_ptr: *mut u8 = self.data.add(source); + let dest_ptr: *mut u8 = self.data.add(destination); - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("EVM Memory") - } + sol_memmove(dest_ptr, src_ptr, length); + } - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - if v.len() % 32 != 0 { - return Err(E::invalid_length(v.len(), &self)); - } + Ok(()) + } +} - Ok(Memory::from_buffer(v)) - } +impl Drop for Memory { + fn drop(&mut self) { + unsafe { + let layout = Layout::from_size_align_unchecked(self.capacity, MEMORY_ALIGN); + acc_allocator().dealloc(self.data, layout); } - - deserializer.deserialize_bytes(BytesVisitor) } } diff --git a/evm_loader/program/src/evm/mod.rs b/evm_loader/program/src/evm/mod.rs index 920c49462..f81fe808a 100644 --- a/evm_loader/program/src/evm/mod.rs +++ b/evm_loader/program/src/evm/mod.rs @@ -1,111 +1,177 @@ #![allow(clippy::trait_duplication_in_bounds)] #![allow(clippy::type_repetition_in_bounds)] #![allow(clippy::unsafe_derive_deserialize)] +#![allow(clippy::future_not_send)] -use std::{marker::PhantomData, ops::Range}; +use std::{fmt::Display, marker::PhantomData, mem::ManuallyDrop, ops::Range}; use ethnum::U256; -use serde::{Deserialize, Serialize}; -use solana_program::log::sol_log_data; +use maybe_async::maybe_async; +pub use buffer::Buffer; + +#[cfg(target_os = "solana")] +use crate::evm::tracing::NoopEventListener; use crate::{ + debug::log_data, error::{build_revert_message, Error, Result}, - evm::opcode::Action, - types::{Address, Transaction}, + evm::{opcode::Action, precompile::is_precompile_address}, + types::{Address, Transaction, Vector}, }; +use crate::{evm::tracing::EventListener, types::boxx::Boxx}; + +use self::{database::Database, memory::Memory, stack::Stack}; mod buffer; pub mod database; mod memory; mod opcode; -mod opcode_table; +pub mod opcode_table; mod precompile; mod stack; -#[cfg(feature = "tracing")] pub mod tracing; mod utils; -use self::{database::Database, memory::Memory, stack::Stack}; -pub use buffer::Buffer; -pub use precompile::is_precompile_address; -pub use precompile::precompile; - macro_rules! tracing_event { - ($x:expr) => { - #[cfg(feature = "tracing")] - crate::evm::tracing::with(|listener| listener.event($x)); - }; - ($condition:expr; $x:expr) => { - #[cfg(feature = "tracing")] - if $condition { - crate::evm::tracing::with(|listener| listener.event($x)); + ($self:expr, $backend:expr, $event:expr) => { + #[cfg(not(target_os = "solana"))] + if let Some(tracer) = &mut $self.tracer { + tracer.event($backend, $event).await?; } }; } -macro_rules! trace_end_step { - ($return_data_vec:expr) => { - #[cfg(feature = "tracing")] - crate::evm::tracing::with(|listener| { - if listener.enable_return_data() { - listener.event(crate::evm::tracing::Event::EndStep { - gas_used: 0_u64, - return_data: $return_data_vec, - }) - } else { - listener.event(crate::evm::tracing::Event::EndStep { - gas_used: 0_u64, - return_data: None, - }) +macro_rules! begin_vm { + ($self:expr, $backend:expr, $context:expr, $chain_id:expr, $input:expr, $opcode:expr) => { + tracing_event!( + $self, + $backend, + crate::evm::tracing::Event::BeginVM { + context: $context, + chain_id: $chain_id, + input: $input.to_vec(), + opcode: $opcode } - }) + ); + }; + ($self:expr, $backend:expr, $context:expr, $chain_id:expr, $input:expr) => { + begin_vm!( + $self, + $backend, + $context, + $chain_id, + $input, + $self.execution_code.get_or_default($self.pc).into() + ); }; +} - ($condition:expr; $return_data_vec:expr) => { - #[cfg(feature = "tracing")] - if $condition { - trace_end_step!($return_data_vec) - } +macro_rules! end_vm { + ($self:expr, $backend:expr, $status:expr) => { + tracing_event!( + $self, + $backend, + crate::evm::tracing::Event::EndVM { + context: $self.context, + chain_id: $self.chain_id, + status: $status + } + ); + }; +} + +macro_rules! begin_step { + ($self:expr, $backend:expr) => { + tracing_event!( + $self, + $backend, + crate::evm::tracing::Event::BeginStep { + context: $self.context, + chain_id: $self.chain_id, + opcode: $self.execution_code.get_or_default($self.pc).into(), + pc: $self.pc, + stack: $self.stack.to_vec(), + memory: $self.memory.to_vec(), + return_data: $self.return_data.to_vec() + } + ); }; } -pub(crate) use trace_end_step; +pub(crate) use begin_step; +pub(crate) use begin_vm; +pub(crate) use end_vm; pub(crate) use tracing_event; -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[repr(C)] pub enum ExitStatus { Stop, - Return(#[serde(with = "serde_bytes")] Vec), - Revert(#[serde(with = "serde_bytes")] Vec), + Return(Vector), + Revert(Vector), Suicide, StepLimit, } -#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +impl Display for ExitStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.status()) + } +} + +impl ExitStatus { + #[must_use] + pub fn status(&self) -> &'static str { + match self { + ExitStatus::Return(_) | ExitStatus::Stop | ExitStatus::Suicide => "succeed", + ExitStatus::Revert(_) => "revert", + ExitStatus::StepLimit => "step limit exceeded", + } + } + + #[must_use] + pub fn is_succeed(&self) -> Option { + match self { + ExitStatus::Stop | ExitStatus::Return(_) | ExitStatus::Suicide => Some(true), + ExitStatus::Revert(_) => Some(false), + ExitStatus::StepLimit => None, + } + } + + #[must_use] + pub fn into_result(self) -> Option> { + match self { + ExitStatus::Return(v) | ExitStatus::Revert(v) => Some(v.to_vec()), + ExitStatus::Stop | ExitStatus::Suicide | ExitStatus::StepLimit => None, + } + } +} + +#[derive(Debug, Eq, PartialEq)] +#[repr(C)] pub enum Reason { Call, Create, } -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone)] +#[repr(C)] pub struct Context { pub caller: Address, pub contract: Address, - #[serde(with = "ethnum::serde::bytes::le")] + pub contract_chain_id: u64, pub value: U256, pub code_address: Option
, } -#[derive(Serialize, Deserialize)] -#[serde(bound = "B: Database")] -pub struct Machine { +#[repr(C)] +pub struct Machine { origin: Address, + chain_id: u64, context: Context, - #[serde(with = "ethnum::serde::bytes::le")] gas_price: U256, - #[serde(with = "ethnum::serde::bytes::le")] gas_limit: U256, execution_code: Buffer, @@ -113,112 +179,103 @@ pub struct Machine { return_data: Buffer, return_range: Range, - stack: stack::Stack, - memory: memory::Memory, + stack: Stack, + memory: Memory, pc: usize, is_static: bool, reason: Reason, - parent: Option>, + parent: Option>, - #[serde(skip)] phantom: PhantomData<*const B>, -} -impl Machine { - pub fn serialize_into(&self, buffer: &mut [u8]) -> Result { - let mut cursor = std::io::Cursor::new(buffer); - - bincode::serialize_into(&mut cursor, &self)?; - - cursor.position().try_into().map_err(Error::from) - } + tracer: Option, +} - pub fn deserialize_from(buffer: &[u8], backend: &B) -> Result { - fn reinit_buffer(buffer: &mut Buffer, backend: &B) { - if let Some((key, range)) = buffer.uninit_data() { - *buffer = backend.map_solana_account(&key, |i| Buffer::from_account(i, range)); - } +#[cfg(target_os = "solana")] +impl Machine { + fn reinit_buffer(buffer: &mut Buffer, backend: &B) { + if let Some((key, range)) = buffer.uninit_data() { + *buffer = + backend.map_solana_account(&key, |i| unsafe { Buffer::from_account(i, range) }); } + } - fn reinit_machine(machine: &mut Machine, backend: &B) { - reinit_buffer(&mut machine.call_data, backend); - reinit_buffer(&mut machine.execution_code, backend); - reinit_buffer(&mut machine.return_data, backend); - - if let Some(parent) = &mut machine.parent { - reinit_machine(parent, backend); + pub fn reinit(&mut self, backend: &B) { + let mut machine = self; + loop { + Self::reinit_buffer(&mut machine.call_data, backend); + Self::reinit_buffer(&mut machine.execution_code, backend); + Self::reinit_buffer(&mut machine.return_data, backend); + match &mut machine.parent { + None => break, + Some(parent) => machine = parent, } } - - let mut evm: Self = bincode::deserialize(buffer)?; - reinit_machine(&mut evm, backend); - - Ok(evm) } +} - pub fn new(trx: Transaction, origin: Address, backend: &mut B) -> Result { - let origin_nonce = backend.nonce(&origin)?; - - if origin_nonce == u64::MAX { - return Err(Error::NonceOverflow(origin)); - } - - if origin_nonce != trx.nonce { - return Err(Error::InvalidTransactionNonce( +impl Machine { + #[maybe_async] + pub async fn new( + trx: &Transaction, + origin: Address, + backend: &mut B, + tracer: Option, + ) -> Result { + let trx_chain_id = trx.chain_id().unwrap_or_else(|| backend.default_chain_id()); + + if backend.balance(origin, trx_chain_id).await? < trx.value() { + return Err(Error::InsufficientBalance( origin, - origin_nonce, - trx.nonce, + trx_chain_id, + trx.value(), )); } - if let Some(chain_id) = trx.chain_id { - if backend.chain_id() != chain_id { - return Err(Error::InvalidChainId(chain_id)); - } - } - - if backend.balance(&origin)? < trx.value { - return Err(Error::InsufficientBalance(origin, trx.value)); - } - - if backend.code_size(&origin)? != 0 { - return Err(Error::SenderHasDeployedCode(origin)); - } - - if trx.target.is_some() { - Self::new_call(trx, origin, backend) + if trx.target().is_some() { + Self::new_call(trx_chain_id, trx, origin, backend, tracer).await } else { - Self::new_create(trx, origin, backend) + Self::new_create(trx_chain_id, trx, origin, backend, tracer).await } } - fn new_call(trx: Transaction, origin: Address, backend: &mut B) -> Result { - assert!(trx.target.is_some()); + #[maybe_async] + async fn new_call( + chain_id: u64, + trx: &Transaction, + origin: Address, + backend: &mut B, + tracer: Option, + ) -> Result { + assert!(trx.target().is_some()); - let target = trx.target.unwrap(); - sol_log_data(&[b"ENTER", b"CALL", target.as_bytes()]); + let target = trx.target().unwrap(); + log_data(&[b"ENTER", b"CALL", target.as_bytes()]); - backend.increment_nonce(origin)?; backend.snapshot(); - backend.transfer(origin, target, trx.value)?; + backend + .transfer(origin, target, chain_id, trx.value()) + .await?; - let execution_code = backend.code(&target)?; + let execution_code = backend.code(target).await?; Ok(Self { origin, + chain_id, context: Context { caller: origin, contract: target, - value: trx.value, + contract_chain_id: backend.contract_chain_id(target).await.unwrap_or(chain_id), + value: trx.value(), code_address: Some(target), }, - gas_price: trx.gas_price, - gas_limit: trx.gas_limit, + gas_price: trx.gas_price(), + gas_limit: trx.gas_limit(), execution_code, - call_data: trx.call_data, + call_data: Buffer::from_slice(trx.call_data()), return_data: Buffer::empty(), return_range: 0..0, stack: Stack::new(), @@ -228,35 +285,47 @@ impl Machine { reason: Reason::Call, parent: None, phantom: PhantomData, + tracer, }) } - fn new_create(trx: Transaction, origin: Address, backend: &mut B) -> Result { - assert!(trx.target.is_none()); - - let target = Address::from_create(&origin, trx.nonce); - sol_log_data(&[b"ENTER", b"CREATE", target.as_bytes()]); - - if (backend.nonce(&target)? != 0) || (backend.code_size(&target)? != 0) { + #[maybe_async] + async fn new_create( + chain_id: u64, + trx: &Transaction, + origin: Address, + backend: &mut B, + tracer: Option, + ) -> Result { + assert!(trx.target().is_none()); + + let target = Address::from_create(&origin, trx.nonce()); + log_data(&[b"ENTER", b"CREATE", target.as_bytes()]); + + if (backend.nonce(target, chain_id).await? != 0) || (backend.code_size(target).await? != 0) + { return Err(Error::DeployToExistingAccount(target, origin)); } - backend.increment_nonce(origin)?; backend.snapshot(); - backend.increment_nonce(target)?; - backend.transfer(origin, target, trx.value)?; + backend.increment_nonce(target, chain_id).await?; + backend + .transfer(origin, target, chain_id, trx.value()) + .await?; Ok(Self { origin, + chain_id, context: Context { caller: origin, contract: target, - value: trx.value, + contract_chain_id: chain_id, + value: trx.value(), code_address: None, }, - gas_price: trx.gas_price, - gas_limit: trx.gas_limit, + gas_price: trx.gas_price(), + gas_limit: trx.gas_limit(), return_data: Buffer::empty(), return_range: 0..0, stack: Stack::new(), @@ -264,85 +333,83 @@ impl Machine { pc: 0_usize, is_static: false, reason: Reason::Create, - execution_code: trx.call_data, + execution_code: Buffer::from_slice(trx.call_data()), call_data: Buffer::empty(), parent: None, phantom: PhantomData, + tracer, }) } - pub fn execute(&mut self, step_limit: u64, backend: &mut B) -> Result<(ExitStatus, u64)> { - assert!(self.execution_code.uninit_data().is_none()); - assert!(self.call_data.uninit_data().is_none()); - assert!(self.return_data.uninit_data().is_none()); - + #[maybe_async] + pub async fn execute( + &mut self, + step_limit: u64, + backend: &mut B, + ) -> Result<(ExitStatus, u64, Option)> { let mut step = 0_u64; - tracing_event!(tracing::Event::BeginVM { - context: self.context, - code: self.execution_code.to_vec() - }); - - let status = loop { - if is_precompile_address(&self.context.contract) { - let value = precompile(&self.context.contract, &self.call_data).unwrap_or_default(); - - backend.commit_snapshot(); - - break ExitStatus::Return(value); - } - - step += 1; - if step > step_limit { - break ExitStatus::StepLimit; + begin_vm!( + self, + backend, + self.context, + self.chain_id, + if self.reason == Reason::Call { + self.call_data.to_vec() + } else { + self.execution_code.to_vec() + }, + if self.reason == Reason::Call { + opcode_table::CALL + } else { + opcode_table::CREATE } + ); - let opcode = self.execution_code.get_or_default(self.pc); - - tracing_event!(tracing::Event::BeginStep { - opcode, - pc: self.pc, - stack: self.stack.to_vec(), - memory: self.memory.to_vec() - }); - - // SAFETY: OPCODES.len() == 256, opcode <= 255 - let opcode_fn = unsafe { Self::OPCODES.get_unchecked(opcode as usize) }; + let status = if is_precompile_address(&self.context.contract) { + let value = Self::precompile(&self.context.contract, &self.call_data).unwrap(); + backend.commit_snapshot(); - let opcode_result = match opcode_fn(self, backend) { - Ok(result) => result, - Err(e) => { - let message = build_revert_message(&e.to_string()); - self.opcode_revert_impl(Buffer::from_slice(&message), backend)? + end_vm!(self, backend, ExitStatus::Return(value.clone())); + ExitStatus::Return(value) + } else { + loop { + step += 1; + if step > step_limit { + break ExitStatus::StepLimit; } - }; - - trace_end_step!(opcode_result != Action::Noop; match &opcode_result { - Action::Return(value) | Action::Revert(value) => Some(value.clone()), - _ => None, - }); - - match opcode_result { - Action::Continue => self.pc += 1, - Action::Jump(target) => self.pc = target, - Action::Stop => break ExitStatus::Stop, - Action::Return(value) => break ExitStatus::Return(value), - Action::Revert(value) => break ExitStatus::Revert(value), - Action::Suicide => break ExitStatus::Suicide, - Action::Noop => {} - }; - }; - tracing_event!(tracing::Event::EndVM { - status: status.clone() - }); + let opcode = self.execution_code.get_or_default(self.pc); + + begin_step!(self, backend); + + let opcode_result = match self.execute_opcode(backend, opcode).await { + Ok(result) => result, + Err(e) => { + let message = build_revert_message(&e.to_string()); + self.opcode_revert_impl(message, backend).await? + } + }; + + match opcode_result { + Action::Continue => self.pc += 1, + Action::Jump(target) => self.pc = target, + Action::Stop => break ExitStatus::Stop, + Action::Return(value) => break ExitStatus::Return(value), + Action::Revert(value) => break ExitStatus::Revert(value), + Action::Suicide => break ExitStatus::Suicide, + Action::Noop => {} + }; + } + }; - Ok((status, step)) + Ok((status, step, self.tracer.take())) } fn fork( &mut self, reason: Reason, + chain_id: u64, context: Context, execution_code: Buffer, call_data: Buffer, @@ -350,6 +417,7 @@ impl Machine { ) { let mut other = Self { origin: self.origin, + chain_id, context, gas_price: self.gas_price, gas_limit: gas_limit.unwrap_or(self.gas_limit), @@ -364,18 +432,21 @@ impl Machine { reason, parent: None, phantom: PhantomData, + tracer: self.tracer.take(), }; core::mem::swap(self, &mut other); - self.parent = Some(Box::new(other)); + self.parent = Some(crate::types::boxx::boxx(other)); } - fn join(&mut self) -> Self { + fn join(&mut self) -> ManuallyDrop> { assert!(self.parent.is_some()); - let mut other = *self.parent.take().unwrap(); - core::mem::swap(self, &mut other); + let mut other = self.parent.take().unwrap(); + core::mem::swap(self, other.as_mut()); + + self.tracer = other.tracer.take(); - other + ManuallyDrop::new(other) } } diff --git a/evm_loader/program/src/evm/opcode.rs b/evm_loader/program/src/evm/opcode.rs index d90926d8e..f7cc9df7b 100644 --- a/evm_loader/program/src/evm/opcode.rs +++ b/evm_loader/program/src/evm/opcode.rs @@ -1,11 +1,23 @@ +#![allow(clippy::needless_pass_by_ref_mut)] + +use std::mem::ManuallyDrop; + /// use ethnum::{I256, U256}; -use solana_program::log::sol_log_data; +use maybe_async::maybe_async; -use super::{database::Database, tracing_event, Context, Machine, Reason}; +use super::{ + begin_vm, + database::{Database, DatabaseExt}, + end_vm, tracing_event, Context, Machine, Reason, +}; +use crate::evm::tracing::EventListener; +use crate::types::vector::VectorSliceExt; +use crate::types::Vector; use crate::{ + debug::log_data, error::{Error, Result}, - evm::{trace_end_step, Buffer}, + evm::Buffer, types::Address, }; @@ -14,15 +26,17 @@ pub enum Action { Continue, Jump(usize), Stop, - Return(Vec), - Revert(Vec), + Return(Vector), + Revert(Vector), Suicide, Noop, } -impl Machine { +#[allow(clippy::unused_async)] +impl Machine { /// Unknown instruction - pub fn opcode_unknown(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_unknown(&mut self, _backend: &mut B) -> Result { Err(Error::UnknownOpcode( self.context.contract, self.execution_code[self.pc], @@ -30,7 +44,8 @@ impl Machine { } /// (u)int256 addition modulo 2**256 - pub fn opcode_add(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_add(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; let c = a.wrapping_add(b); @@ -41,7 +56,8 @@ impl Machine { } /// (u)int256 multiplication modulo 2**256 - pub fn opcode_mul(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_mul(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; let c = a.wrapping_mul(b); @@ -52,7 +68,8 @@ impl Machine { } /// (u)int256 subtraction modulo 2**256 - pub fn opcode_sub(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_sub(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; let c = a.wrapping_sub(b); @@ -63,7 +80,8 @@ impl Machine { } /// uint256 division - pub fn opcode_div(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_div(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; @@ -78,7 +96,8 @@ impl Machine { } /// int256 division - pub fn opcode_sdiv(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_sdiv(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_i256()?; let b = self.stack.pop_i256()?; @@ -94,7 +113,8 @@ impl Machine { } /// uint256 modulus - pub fn opcode_mod(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_mod(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; @@ -109,7 +129,8 @@ impl Machine { } /// int256 modulus - pub fn opcode_smod(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_smod(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_i256()?; let b = self.stack.pop_i256()?; @@ -126,7 +147,8 @@ impl Machine { /// (u)int256 addition modulo M /// (a + b) % m /// - pub fn opcode_addmod(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_addmod(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; let m = self.stack.pop_u256()?; @@ -157,7 +179,8 @@ impl Machine { /// (u)int256 multiplication modulo M /// (a * b) % m /// - pub fn opcode_mulmod(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_mulmod(&mut self, _backend: &mut B) -> Result { let mut a = self.stack.pop_u256()?; let mut b = self.stack.pop_u256()?; let m = self.stack.pop_u256()?; @@ -202,7 +225,8 @@ impl Machine { /// uint256 exponentiation modulo 2**256 /// a ** b - pub fn opcode_exp(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_exp(&mut self, _backend: &mut B) -> Result { let mut a = self.stack.pop_u256()?; let mut b = self.stack.pop_u256()?; @@ -231,7 +255,8 @@ impl Machine { } /// sign extends x from (b + 1) * 8 bits to 256 bits. - pub fn opcode_signextend(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_signextend(&mut self, _backend: &mut B) -> Result { let b = self.stack.pop_u256()?; let x = self.stack.pop_u256()?; @@ -256,7 +281,8 @@ impl Machine { /// uint256 comparison /// a < b - pub fn opcode_lt(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_lt(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; @@ -267,7 +293,8 @@ impl Machine { /// uint256 comparison /// a > b - pub fn opcode_gt(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_gt(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; @@ -278,7 +305,8 @@ impl Machine { /// int256 comparison /// a < b - pub fn opcode_slt(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_slt(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_i256()?; let b = self.stack.pop_i256()?; self.stack.push_bool(a < b)?; @@ -288,7 +316,8 @@ impl Machine { /// int256 comparison /// a > b - pub fn opcode_sgt(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_sgt(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_i256()?; let b = self.stack.pop_i256()?; self.stack.push_bool(a > b)?; @@ -298,7 +327,8 @@ impl Machine { /// (u)int256 equality /// a == b - pub fn opcode_eq(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_eq(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; @@ -309,7 +339,8 @@ impl Machine { /// (u)int256 is zero /// a == 0 - pub fn opcode_iszero(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_iszero(&mut self, _backend: &mut B) -> Result { let result = { let a = self.stack.pop_array()?; a == &[0_u8; 32] @@ -321,7 +352,8 @@ impl Machine { } /// 256-bit bitwise and - pub fn opcode_and(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_and(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; @@ -331,7 +363,8 @@ impl Machine { } /// 256-bit bitwise or - pub fn opcode_or(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_or(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; @@ -341,7 +374,8 @@ impl Machine { } /// 256-bit bitwise xor - pub fn opcode_xor(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_xor(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; let b = self.stack.pop_u256()?; @@ -351,7 +385,8 @@ impl Machine { } /// 256-bit bitwise not - pub fn opcode_not(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_not(&mut self, _backend: &mut B) -> Result { let a = self.stack.pop_u256()?; self.stack.push_u256(!a)?; @@ -359,7 +394,8 @@ impl Machine { } /// ith byte of (u)int256 x, counting from most significant byte - pub fn opcode_byte(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_byte(&mut self, _backend: &mut B) -> Result { let result = { let i = self.stack.pop_u256()?; let x = self.stack.pop_array()?; @@ -377,7 +413,8 @@ impl Machine { } /// 256-bit shift left - pub fn opcode_shl(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_shl(&mut self, _backend: &mut B) -> Result { let shift = self.stack.pop_u256()?; let value = self.stack.pop_u256()?; @@ -391,7 +428,8 @@ impl Machine { } /// 256-bit shift right - pub fn opcode_shr(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_shr(&mut self, _backend: &mut B) -> Result { let shift = self.stack.pop_u256()?; let value = self.stack.pop_u256()?; @@ -405,7 +443,8 @@ impl Machine { } /// arithmetic int256 shift right - pub fn opcode_sar(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_sar(&mut self, _backend: &mut B) -> Result { let (shift, value) = { let shift = self.stack.pop_u256()?; let value = self.stack.pop_i256()?; @@ -422,7 +461,8 @@ impl Machine { } /// hash = keccak256(memory[offset:offset+length]) - pub fn opcode_sha3(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_sha3(&mut self, _backend: &mut B) -> Result { use solana_program::keccak::{hash, Hash}; let offset = self.stack.pop_usize()?; @@ -437,17 +477,19 @@ impl Machine { } /// address of the executing contract - pub fn opcode_address(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_address(&mut self, _backend: &mut B) -> Result { self.stack.push_address(&self.context.contract)?; Ok(Action::Continue) } /// address balance in wei - pub fn opcode_balance(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_balance(&mut self, backend: &mut B) -> Result { let balance = { let address = self.stack.pop_address()?; - backend.balance(address)? + backend.balance(address, self.chain_id).await? }; self.stack.push_u256(balance)?; @@ -457,7 +499,8 @@ impl Machine { /// transaction origin address /// tx.origin - pub fn opcode_origin(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_origin(&mut self, _backend: &mut B) -> Result { self.stack.push_address(&self.origin)?; Ok(Action::Continue) @@ -465,7 +508,8 @@ impl Machine { /// message caller address /// msg.caller - pub fn opcode_caller(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_caller(&mut self, _backend: &mut B) -> Result { self.stack.push_address(&self.context.caller)?; Ok(Action::Continue) @@ -473,7 +517,8 @@ impl Machine { /// message funds in wei /// msg.value - pub fn opcode_callvalue(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_callvalue(&mut self, _backend: &mut B) -> Result { self.stack.push_u256(self.context.value)?; Ok(Action::Continue) @@ -481,7 +526,8 @@ impl Machine { /// reads a (u)int256 from message data /// msg.data[i:i+32] - pub fn opcode_calldataload(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_calldataload(&mut self, _backend: &mut B) -> Result { let index = self.stack.pop_usize()?; if let Some(buffer) = self.call_data.get(index..index + 32) { @@ -502,14 +548,16 @@ impl Machine { /// message data length in bytes /// msg.data.size - pub fn opcode_calldatasize(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_calldatasize(&mut self, _backend: &mut B) -> Result { self.stack.push_usize(self.call_data.len())?; Ok(Action::Continue) } /// copy message data to memory - pub fn opcode_calldatacopy(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_calldatacopy(&mut self, _backend: &mut B) -> Result { let memory_offset = self.stack.pop_usize()?; let data_offset = self.stack.pop_usize()?; let length = self.stack.pop_usize()?; @@ -522,14 +570,16 @@ impl Machine { /// length of the executing contract's code in bytes /// address(this).code.size - pub fn opcode_codesize(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_codesize(&mut self, _backend: &mut B) -> Result { self.stack.push_usize(self.execution_code.len())?; Ok(Action::Continue) } /// copy executing contract's bytecode - pub fn opcode_codecopy(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_codecopy(&mut self, _backend: &mut B) -> Result { let memory_offset = self.stack.pop_usize()?; let data_offset = self.stack.pop_usize()?; let length = self.stack.pop_usize()?; @@ -542,7 +592,8 @@ impl Machine { /// gas price of the executing transaction, in wei per unit of gas /// tx.gasprice - pub fn opcode_gasprice(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_gasprice(&mut self, _backend: &mut B) -> Result { self.stack.push_u256(self.gas_price)?; Ok(Action::Continue) @@ -550,10 +601,11 @@ impl Machine { /// length of the contract bytecode at addr, in bytes /// address(addr).code.size - pub fn opcode_extcodesize(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_extcodesize(&mut self, backend: &mut B) -> Result { let code_size = { let address = self.stack.pop_address()?; - backend.code_size(address)? + backend.code_size(address).await? }; self.stack.push_usize(code_size)?; @@ -562,13 +614,14 @@ impl Machine { } /// copy contract's bytecode - pub fn opcode_extcodecopy(&mut self, backend: &mut B) -> Result { - let address = *self.stack.pop_address()?; + #[maybe_async] + pub async fn opcode_extcodecopy(&mut self, backend: &mut B) -> Result { + let address = self.stack.pop_address()?; let memory_offset = self.stack.pop_usize()?; let data_offset = self.stack.pop_usize()?; let length = self.stack.pop_usize()?; - let code = backend.code(&address)?; + let code = backend.code(address).await?; self.memory .write_buffer(memory_offset, length, &code, data_offset)?; @@ -577,14 +630,16 @@ impl Machine { } /// Byzantium hardfork, EIP-211: the size of the returned data from the last external call, in bytes - pub fn opcode_returndatasize(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_returndatasize(&mut self, _backend: &mut B) -> Result { self.stack.push_usize(self.return_data.len())?; Ok(Action::Continue) } /// Byzantium hardfork, EIP-211: copy returned data - pub fn opcode_returndatacopy(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_returndatacopy(&mut self, _backend: &mut B) -> Result { let memory_offset = self.stack.pop_usize()?; let data_offset = self.stack.pop_usize()?; let length = self.stack.pop_usize()?; @@ -600,10 +655,11 @@ impl Machine { } /// Constantinople hardfork, EIP-1052: hash of the contract bytecode at addr - pub fn opcode_extcodehash(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_extcodehash(&mut self, backend: &mut B) -> Result { let code_hash = { let address = self.stack.pop_address()?; - backend.code_hash(address)? + backend.code_hash(address, self.chain_id).await? }; self.stack.push_array(&code_hash)?; @@ -613,11 +669,12 @@ impl Machine { /// hash of the specific block, only valid for the 256 most recent blocks, excluding the current one /// Solana limits to 150 most recent blocks - pub fn opcode_blockhash(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_blockhash(&mut self, backend: &mut B) -> Result { let block_hash = { let block_number = self.stack.pop_u256()?; - backend.block_hash(block_number)? + backend.block_hash(block_number).await? }; self.stack.push_array(&block_hash)?; @@ -627,14 +684,16 @@ impl Machine { /// address of the current block's miner /// NOT SUPPORTED - pub fn opcode_coinbase(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_coinbase(&mut self, _backend: &mut B) -> Result { self.stack.push_zero()?; Ok(Action::Continue) } /// current block's Unix timestamp in seconds - pub fn opcode_timestamp(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_timestamp(&mut self, backend: &mut B) -> Result { let timestamp = backend.block_timestamp()?; self.stack.push_u256(timestamp)?; @@ -643,7 +702,8 @@ impl Machine { } /// current block's number - pub fn opcode_number(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_number(&mut self, backend: &mut B) -> Result { let block_number = backend.block_number()?; self.stack.push_u256(block_number)?; @@ -653,7 +713,8 @@ impl Machine { /// current block's difficulty /// NOT SUPPORTED - pub fn opcode_difficulty(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_difficulty(&mut self, _backend: &mut B) -> Result { self.stack.push_zero()?; Ok(Action::Continue) @@ -661,15 +722,17 @@ impl Machine { /// current block's gas limit /// NOT SUPPORTED - pub fn opcode_gaslimit(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_gaslimit(&mut self, _backend: &mut B) -> Result { self.stack.push_u256(U256::MAX)?; Ok(Action::Continue) } /// Istanbul hardfork, EIP-1344: current network's chain id - pub fn opcode_chainid(&mut self, backend: &mut B) -> Result { - let chain_id = backend.chain_id(); + #[maybe_async] + pub async fn opcode_chainid(&mut self, _backend: &mut B) -> Result { + let chain_id = self.chain_id.into(); self.stack.push_u256(chain_id)?; @@ -677,31 +740,38 @@ impl Machine { } /// Istanbul hardfork, EIP-1884: balance of the executing contract in wei - pub fn opcode_selfbalance(&mut self, backend: &mut B) -> Result { - let balance = backend.balance(&self.context.contract)?; + #[maybe_async] + pub async fn opcode_selfbalance(&mut self, backend: &mut B) -> Result { + let balance = backend + .balance(self.context.contract, self.chain_id) + .await?; self.stack.push_u256(balance)?; Ok(Action::Continue) } - /// London hardfork, EIP-3198: current block's base fee - /// NOT SUPPORTED - pub fn opcode_basefee(&mut self, _backend: &mut B) -> Result { - self.stack.push_zero()?; + /// London hardfork, EIP-3198: current block's base fee, taken from the transaction. + /// N.B. for DynamicFee transaction (EIP-1559), gas_price here is equal to: + /// `max_fee_per_gas` - `max_priority_fee_per_gas`. + #[maybe_async] + pub async fn opcode_basefee(&mut self, _backend: &mut B) -> Result { + self.stack.push_u256(self.gas_price)?; Ok(Action::Continue) } /// pops a (u)int256 off the stack and discards it - pub fn opcode_pop(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_pop(&mut self, _backend: &mut B) -> Result { self.stack.discard()?; Ok(Action::Continue) } /// reads a (u)int256 from memory - pub fn opcode_mload(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_mload(&mut self, _backend: &mut B) -> Result { let offset = self.stack.pop_usize()?; let value = self.memory.read_32(offset)?; @@ -711,7 +781,8 @@ impl Machine { } /// writes a (u)int256 to memory - pub fn opcode_mstore(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_mstore(&mut self, _backend: &mut B) -> Result { let offset = self.stack.pop_usize()?; let value = self.stack.pop_array()?; @@ -721,7 +792,8 @@ impl Machine { } /// writes a uint8 to memory - pub fn opcode_mstore8(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_mstore8(&mut self, _backend: &mut B) -> Result { let offset = self.stack.pop_usize()?; let value = self.stack.pop_array()?; @@ -730,12 +802,23 @@ impl Machine { Ok(Action::Continue) } + /// copying memory areas + #[maybe_async] + pub async fn opcode_mcopy(&mut self, _backend: &mut B) -> Result { + let target = self.stack.pop_usize()?; + let source = self.stack.pop_usize()?; + let length = self.stack.pop_usize()?; + + self.memory.copy_within(source, target, length)?; + + Ok(Action::Continue) + } + /// reads a (u)int256 from storage - pub fn opcode_sload(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_sload(&mut self, backend: &mut B) -> Result { let index = self.stack.pop_u256()?; - let value = backend.storage(&self.context.contract, &index)?; - - tracing_event!(super::tracing::Event::StorageAccess { index, value }); + let value = backend.storage(self.context.contract, index).await?; self.stack.push_array(&value)?; @@ -743,7 +826,8 @@ impl Machine { } /// writes a (u)int256 to storage - pub fn opcode_sstore(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_sstore(&mut self, backend: &mut B) -> Result { if self.is_static { return Err(Error::StaticModeViolation(self.context.contract)); } @@ -751,16 +835,44 @@ impl Machine { let index = self.stack.pop_u256()?; let value = *self.stack.pop_array()?; - tracing_event!(super::tracing::Event::StorageSet { index, value }); - tracing_event!(super::tracing::Event::StorageAccess { index, value }); + backend + .set_storage(self.context.contract, index, value) + .await?; + + Ok(Action::Continue) + } + + /// reads a (u)int256 from transient storage + #[maybe_async] + pub async fn opcode_tload(&mut self, backend: &mut B) -> Result { + let index = self.stack.pop_u256()?; + let value = backend + .transient_storage(self.context.contract, index) + .await?; + + self.stack.push_array(&value)?; + + Ok(Action::Continue) + } + + /// writes a (u)int256 to transient storage + #[maybe_async] + pub async fn opcode_tstore(&mut self, backend: &mut B) -> Result { + if self.is_static { + return Err(Error::StaticModeViolation(self.context.contract)); + } - backend.set_storage(self.context.contract, index, value)?; + let index = self.stack.pop_u256()?; + let value = *self.stack.pop_array()?; + + backend.set_transient_storage(self.context.contract, index, value)?; Ok(Action::Continue) } /// unconditional jump - pub fn opcode_jump(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_jump(&mut self, _backend: &mut B) -> Result { const JUMPDEST: u8 = 0x5B; let value = self.stack.pop_usize()?; @@ -773,7 +885,8 @@ impl Machine { } /// conditional jump - pub fn opcode_jumpi(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_jumpi(&mut self, _backend: &mut B) -> Result { const JUMPDEST: u8 = 0x5B; let value = self.stack.pop_usize()?; @@ -791,33 +904,38 @@ impl Machine { } /// program counter - pub fn opcode_pc(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_pc(&mut self, _backend: &mut B) -> Result { self.stack.push_usize(self.pc)?; Ok(Action::Continue) } /// memory size - pub fn opcode_msize(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_msize(&mut self, _backend: &mut B) -> Result { self.stack.push_usize(self.memory.size())?; Ok(Action::Continue) } /// remaining gas - pub fn opcode_gas(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_gas(&mut self, _backend: &mut B) -> Result { self.stack.push_u256(self.gas_limit)?; Ok(Action::Continue) } /// metadata to annotate possible jump destinations - pub fn opcode_jumpdest(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_jumpdest(&mut self, _backend: &mut B) -> Result { Ok(Action::Continue) } /// Place zero on stack - pub fn opcode_push_0(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_push_0(&mut self, _backend: &mut B) -> Result { self.stack.push_zero()?; Ok(Action::Continue) @@ -825,7 +943,8 @@ impl Machine { /// Place 1 byte item on stack /// ~50% of contract bytecode are PUSH opcodes - pub fn opcode_push_1(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_push_1(&mut self, _backend: &mut B) -> Result { if self.execution_code.len() <= self.pc + 1 { return Err(Error::PushOutOfBounds(self.context.contract)); } @@ -838,7 +957,8 @@ impl Machine { } /// Place 2-31 byte item on stack. - pub fn opcode_push_2_31(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_push_2_31(&mut self, _backend: &mut B) -> Result { if self.execution_code.len() <= self.pc + 1 + N { return Err(Error::PushOutOfBounds(self.context.contract)); } @@ -854,7 +974,8 @@ impl Machine { } /// Place 32 byte item on stack - pub fn opcode_push_32(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_push_32(&mut self, _backend: &mut B) -> Result { if self.execution_code.len() <= self.pc + 1 + 32 { return Err(Error::PushOutOfBounds(self.context.contract)); } @@ -871,24 +992,28 @@ impl Machine { /// Duplicate Nth stack item /// ~25% of contract bytecode are DUP and SWAP opcodes - pub fn opcode_dup_1_16(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_dup_1_16(&mut self, _backend: &mut B) -> Result { self.stack.dup_1_16::()?; Ok(Action::Continue) } /// Exchange 1st and (N+1)th stack item - pub fn opcode_swap_1_16(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_swap_1_16(&mut self, _backend: &mut B) -> Result { self.stack.swap_1_16::()?; Ok(Action::Continue) } /// Append log record with N topics - #[rustfmt::skip] - pub fn opcode_log_0_4(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_log_0_4(&mut self, backend: &mut B) -> Result { + let address = self.context.contract; + if self.is_static { - return Err(Error::StaticModeViolation(self.context.contract)); + return Err(Error::StaticModeViolation(address)); } let offset = self.stack.pop_usize()?; @@ -904,22 +1029,14 @@ impl Machine { topics }; - let address = self.context.contract.as_bytes(); - - match N { - 0 => sol_log_data(&[b"LOG0", address, &[0], data]), - 1 => sol_log_data(&[b"LOG1", address, &[1], &topics[0], data]), - 2 => sol_log_data(&[b"LOG2", address, &[2], &topics[0], &topics[1], data]), - 3 => sol_log_data(&[b"LOG3", address, &[3], &topics[0], &topics[1], &topics[2], data]), - 4 => sol_log_data(&[b"LOG4", address, &[4], &topics[0], &topics[1], &topics[2], &topics[3], data]), - _ => unreachable!(), - } + backend.collect_log(address.as_bytes(), topics, data); Ok(Action::Continue) } /// Create a new account with associated code. - pub fn opcode_create(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_create(&mut self, backend: &mut B) -> Result { if self.is_static { return Err(Error::StaticModeViolation(self.context.contract)); } @@ -929,15 +1046,21 @@ impl Machine { let length = self.stack.pop_usize()?; let created_address = { - let nonce = backend.nonce(&self.context.contract)?; - Address::from_create(&self.context.contract, nonce) + let source = self.context.contract; + let chain_id = self.context.contract_chain_id; + + let nonce = backend.nonce(source, chain_id).await?; + + Address::from_create(&source, nonce) }; self.opcode_create_impl(created_address, value, offset, length, backend) + .await } /// Constantinople harfork, EIP-1014: creates a create a new account with a deterministic address - pub fn opcode_create2(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_create2(&mut self, backend: &mut B) -> Result { if self.is_static { return Err(Error::StaticModeViolation(self.context.contract)); } @@ -953,9 +1076,11 @@ impl Machine { }; self.opcode_create_impl(created_address, value, offset, length, backend) + .await } - fn opcode_create_impl( + #[maybe_async] + async fn opcode_create_impl( &mut self, address: Address, value: U256, @@ -963,11 +1088,16 @@ impl Machine { length: usize, backend: &mut B, ) -> Result { - if backend.nonce(&self.context.contract)? == u64::MAX { + let chain_id = self.context.contract_chain_id; + + let contract_nonce = backend.nonce(self.context.contract, chain_id).await?; + if contract_nonce == u64::MAX { return Err(Error::NonceOverflow(self.context.contract)); } - backend.increment_nonce(self.context.contract)?; + backend + .increment_nonce(self.context.contract, chain_id) + .await?; self.return_data = Buffer::empty(); self.return_range = 0..0; @@ -977,38 +1107,44 @@ impl Machine { let context = Context { caller: self.context.contract, contract: address, + contract_chain_id: chain_id, value, code_address: None, }; - tracing_event!(super::tracing::Event::BeginVM { - context, - code: init_code.to_vec() - }); + begin_vm!(self, backend, context, chain_id, init_code); - self.fork(Reason::Create, context, init_code, Buffer::empty(), None); + self.fork( + Reason::Create, + chain_id, + context, + init_code, + Buffer::empty(), + None, + ); backend.snapshot(); - sol_log_data(&[b"ENTER", b"CREATE", address.as_bytes()]); + log_data(&[b"ENTER", b"CREATE", address.as_bytes()]); - if (backend.nonce(&address)? != 0) || (backend.code_size(&address)? != 0) { + if (backend.nonce(address, chain_id).await? != 0) + || (backend.code_size(address).await? != 0) + { return Err(Error::DeployToExistingAccount(address, self.context.caller)); } - if backend.balance(&self.context.caller)? < value { - return Err(Error::InsufficientBalance(self.context.caller, value)); - } - - backend.increment_nonce(address)?; - backend.transfer(self.context.caller, address, value)?; + backend.increment_nonce(address, chain_id).await?; + backend + .transfer(self.context.caller, address, chain_id, value) + .await?; Ok(Action::Noop) } /// Message-call into an account - pub fn opcode_call(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_call(&mut self, backend: &mut B) -> Result { let gas_limit = self.stack.pop_u256()?; - let address = *self.stack.pop_address()?; + let address = self.stack.pop_address()?; let value = self.stack.pop_u256()?; let args_offset = self.stack.pop_usize()?; let args_length = self.stack.pop_usize()?; @@ -1019,42 +1155,47 @@ impl Machine { self.return_range = return_offset..(return_offset + return_length); let call_data = self.memory.read_buffer(args_offset, args_length)?; - let code = backend.code(&address)?; + let code = backend.code(address).await?; + let chain_id = self.context.contract_chain_id; let context = Context { caller: self.context.contract, contract: address, + contract_chain_id: backend.contract_chain_id(address).await.unwrap_or(chain_id), value, code_address: Some(address), }; - tracing_event!(super::tracing::Event::BeginVM { - context, - code: code.to_vec() - }); + begin_vm!(self, backend, context, chain_id, call_data); - self.fork(Reason::Call, context, code, call_data, Some(gas_limit)); + self.fork( + Reason::Call, + chain_id, + context, + code, + call_data, + Some(gas_limit), + ); backend.snapshot(); - sol_log_data(&[b"ENTER", b"CALL", address.as_bytes()]); + log_data(&[b"ENTER", b"CALL", address.as_bytes()]); if self.is_static && (value != U256::ZERO) { return Err(Error::StaticModeViolation(self.context.caller)); } - if backend.balance(&self.context.caller)? < value { - return Err(Error::InsufficientBalance(self.context.caller, value)); - } - - backend.transfer(self.context.caller, self.context.contract, value)?; + backend + .transfer(self.context.caller, self.context.contract, chain_id, value) + .await?; - self.opcode_call_precompile_impl(backend, &address) + self.opcode_call_precompile_impl(backend, &address).await } /// Message-call into this account with an alternative account’s code - pub fn opcode_callcode(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_callcode(&mut self, backend: &mut B) -> Result { let gas_limit = self.stack.pop_u256()?; - let address = *self.stack.pop_address()?; + let address = self.stack.pop_address()?; let value = self.stack.pop_u256()?; let args_offset = self.stack.pop_usize()?; let args_length = self.stack.pop_usize()?; @@ -1065,37 +1206,47 @@ impl Machine { self.return_range = return_offset..(return_offset + return_length); let call_data = self.memory.read_buffer(args_offset, args_length)?; - let code = backend.code(&address)?; + let code = backend.code(address).await?; + let chain_id = self.context.contract_chain_id; let context = Context { - caller: self.context.contract, - contract: self.context.contract, value, code_address: Some(address), + caller: self.context.contract, + ..self.context }; - tracing_event!(super::tracing::Event::BeginVM { - context, - code: code.to_vec() - }); + begin_vm!(self, backend, context, chain_id, call_data); - self.fork(Reason::Call, context, code, call_data, Some(gas_limit)); + self.fork( + Reason::Call, + chain_id, + context, + code, + call_data, + Some(gas_limit), + ); backend.snapshot(); - sol_log_data(&[b"ENTER", b"CALLCODE", address.as_bytes()]); + log_data(&[b"ENTER", b"CALLCODE", address.as_bytes()]); - if backend.balance(&self.context.caller)? < value { - return Err(Error::InsufficientBalance(self.context.caller, value)); + if backend.balance(self.context.caller, chain_id).await? < value { + return Err(Error::InsufficientBalance( + self.context.caller, + chain_id, + value, + )); } - self.opcode_call_precompile_impl(backend, &address) + self.opcode_call_precompile_impl(backend, &address).await } /// Homestead hardfork, EIP-7: Message-call into this account with an alternative account’s code, /// but persisting the current values for sender and value - pub fn opcode_delegatecall(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_delegatecall(&mut self, backend: &mut B) -> Result { let gas_limit = self.stack.pop_u256()?; - let address = *self.stack.pop_address()?; + let address = self.stack.pop_address()?; let args_offset = self.stack.pop_usize()?; let args_length = self.stack.pop_usize()?; let return_offset = self.stack.pop_usize()?; @@ -1105,31 +1256,36 @@ impl Machine { self.return_range = return_offset..(return_offset + return_length); let call_data = self.memory.read_buffer(args_offset, args_length)?; - let code = backend.code(&address)?; + let code = backend.code(address).await?; let context = Context { code_address: Some(address), ..self.context }; - tracing_event!(super::tracing::Event::BeginVM { - context, - code: code.to_vec() - }); + begin_vm!(self, backend, context, self.chain_id, call_data); - self.fork(Reason::Call, context, code, call_data, Some(gas_limit)); + self.fork( + Reason::Call, + self.chain_id, + context, + code, + call_data, + Some(gas_limit), + ); backend.snapshot(); - sol_log_data(&[b"ENTER", b"DELEGATECALL", address.as_bytes()]); + log_data(&[b"ENTER", b"DELEGATECALL", address.as_bytes()]); - self.opcode_call_precompile_impl(backend, &address) + self.opcode_call_precompile_impl(backend, &address).await } /// Byzantium hardfork, EIP-214: Static message-call into an account /// Disallowed contract creation, event emission, storage modification and contract destruction - pub fn opcode_staticcall(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_staticcall(&mut self, backend: &mut B) -> Result { let gas_limit = self.stack.pop_u256()?; - let address = *self.stack.pop_address()?; + let address = self.stack.pop_address()?; let args_offset = self.stack.pop_usize()?; let args_length = self.stack.pop_usize()?; let return_offset = self.stack.pop_usize()?; @@ -1139,92 +1295,104 @@ impl Machine { self.return_range = return_offset..(return_offset + return_length); let call_data = self.memory.read_buffer(args_offset, args_length)?; - let code = backend.code(&address)?; + let code = backend.code(address).await?; + let chain_id = self.context.contract_chain_id; let context = Context { caller: self.context.contract, contract: address, + contract_chain_id: backend.contract_chain_id(address).await.unwrap_or(chain_id), value: U256::ZERO, code_address: Some(address), }; - tracing_event!(super::tracing::Event::BeginVM { - context, - code: code.to_vec() - }); + begin_vm!(self, backend, context, chain_id, call_data); - self.fork(Reason::Call, context, code, call_data, Some(gas_limit)); + self.fork( + Reason::Call, + chain_id, + context, + code, + call_data, + Some(gas_limit), + ); self.is_static = true; backend.snapshot(); - sol_log_data(&[b"ENTER", b"STATICCALL", address.as_bytes()]); + log_data(&[b"ENTER", b"STATICCALL", address.as_bytes()]); - self.opcode_call_precompile_impl(backend, &address) + self.opcode_call_precompile_impl(backend, &address).await } /// Call precompile contract. /// Returns `Action::Noop` if address is not a precompile - fn opcode_call_precompile_impl( + #[maybe_async] + async fn opcode_call_precompile_impl( &mut self, backend: &mut B, address: &Address, ) -> Result { - let result = Self::precompile(address, &self.call_data).map(Ok); - let result = result.or_else(|| { - backend.precompile_extension(&self.context, address, &self.call_data, self.is_static) - }); + let result = match Self::precompile(address, &self.call_data).map(Ok) { + Some(x) => Some(x), + None => { + backend + .precompile_extension(&self.context, address, &self.call_data, self.is_static) + .await + } + }; - if result.is_none() { - return Ok(Action::Noop); + if let Some(return_data) = result.transpose()? { + return self.opcode_return_impl(return_data, backend).await; } - let result = result.unwrap()?; - let return_data = Buffer::from_slice(&result); - - self.opcode_return_impl(return_data, backend) + Ok(Action::Noop) } /// Halt execution returning output data - pub fn opcode_return(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_return(&mut self, backend: &mut B) -> Result { let offset = self.stack.pop_usize()?; let length = self.stack.pop_usize()?; - let return_data = self.memory.read_buffer(offset, length)?; + let return_data = self.memory.read(offset, length)?.to_vector(); - self.opcode_return_impl(return_data, backend) + self.opcode_return_impl(return_data, backend).await } /// Halt execution returning output data - pub fn opcode_return_impl( + #[maybe_async] + pub async fn opcode_return_impl( &mut self, - mut return_data: Buffer, + return_data: Vector, backend: &mut B, ) -> Result { if self.reason == Reason::Create { - let code = std::mem::take(&mut return_data); - backend.set_code(self.context.contract, code)?; + backend + .set_code(self.context.contract, self.chain_id, return_data.clone()) + .await?; } backend.commit_snapshot(); - sol_log_data(&[b"EXIT", b"RETURN"]); + log_data(&[b"EXIT", b"RETURN"]); + + end_vm!( + self, + backend, + super::ExitStatus::Return(return_data.clone()) + ); if self.parent.is_none() { - return Ok(Action::Return(return_data.to_vec())); + return Ok(Action::Return(return_data)); } - trace_end_step!(Some(return_data.to_vec())); - tracing_event!(super::tracing::Event::EndVM { - status: super::ExitStatus::Return(return_data.to_vec()) - }); - - let returned = self.join(); + let mut returned = self.join(); match returned.reason { Reason::Call => { self.memory.write_range(&self.return_range, &return_data)?; self.stack.push_bool(true)?; // success - self.return_data = return_data; + self.return_data = Buffer::from_vector(return_data); } Reason::Create => { let address = returned.context.contract; @@ -1232,33 +1400,41 @@ impl Machine { } } + unsafe { ManuallyDrop::drop(&mut returned) }; Ok(Action::Continue) } /// Byzantium hardfork, EIP-140: Halt execution reverting state changes but returning data - pub fn opcode_revert(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_revert(&mut self, backend: &mut B) -> Result { let offset = self.stack.pop_usize()?; let length = self.stack.pop_usize()?; - let return_data = self.memory.read_buffer(offset, length)?; + let return_data = self.memory.read(offset, length)?.to_vector(); - self.opcode_revert_impl(return_data, backend) + self.opcode_revert_impl(return_data, backend).await } - pub fn opcode_revert_impl(&mut self, return_data: Buffer, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_revert_impl( + &mut self, + return_data: Vector, + backend: &mut B, + ) -> Result { + log_data(&[b"EXIT", b"REVERT", &return_data]); backend.revert_snapshot(); - sol_log_data(&[b"EXIT", b"REVERT", &return_data]); + + end_vm!( + self, + backend, + super::ExitStatus::Revert(return_data.clone()) + ); if self.parent.is_none() { - return Ok(Action::Revert(return_data.to_vec())); + return Ok(Action::Revert(return_data)); } - trace_end_step!(Some(return_data.to_vec())); - tracing_event!(super::tracing::Event::EndVM { - status: super::ExitStatus::Revert(return_data.to_vec()) - }); - - let returned = self.join(); + let mut returned = self.join(); match returned.reason { Reason::Call => { self.memory.write_range(&self.return_range, &return_data)?; @@ -1269,13 +1445,18 @@ impl Machine { } } - self.return_data = return_data; + self.return_data = Buffer::from_vector(return_data); + + unsafe { + ManuallyDrop::drop(&mut returned); + } Ok(Action::Continue) } /// Invalid instruction - pub fn opcode_invalid(&mut self, _backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_invalid(&mut self, _backend: &mut B) -> Result { Err(Error::InvalidOpcode( self.context.contract, self.execution_code[self.pc], @@ -1283,30 +1464,30 @@ impl Machine { } /// Halt execution, destroys the contract and send all funds to address - pub fn opcode_selfdestruct(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_sendall(&mut self, backend: &mut B) -> Result { if self.is_static { return Err(Error::StaticModeViolation(self.context.contract)); } - let address = *self.stack.pop_address()?; + let address = self.stack.pop_address()?; - let value = backend.balance(&self.context.contract)?; - backend.transfer(self.context.contract, address, value)?; - backend.selfdestruct(self.context.contract)?; + let chain_id = self.context.contract_chain_id; + let value = backend.balance(self.context.contract, chain_id).await?; + backend + .transfer(self.context.contract, address, chain_id, value) + .await?; backend.commit_snapshot(); - sol_log_data(&[b"EXIT", b"SELFDESTRUCT"]); + log_data(&[b"EXIT", b"SENDALL"]); + + end_vm!(self, backend, super::ExitStatus::Suicide); if self.parent.is_none() { return Ok(Action::Suicide); } - trace_end_step!(None); - tracing_event!(super::tracing::Event::EndVM { - status: super::ExitStatus::Suicide - }); - - let returned = self.join(); + let mut returned = self.join(); match returned.reason { Reason::Call => { self.memory.write_range(&self.return_range, &[])?; @@ -1317,24 +1498,26 @@ impl Machine { } } + unsafe { + ManuallyDrop::drop(&mut returned); + } + Ok(Action::Continue) } /// Halts execution of the contract - pub fn opcode_stop(&mut self, backend: &mut B) -> Result { + #[maybe_async] + pub async fn opcode_stop(&mut self, backend: &mut B) -> Result { backend.commit_snapshot(); - sol_log_data(&[b"EXIT", b"STOP"]); + log_data(&[b"EXIT", b"STOP"]); + + end_vm!(self, backend, super::ExitStatus::Stop); if self.parent.is_none() { return Ok(Action::Stop); } - trace_end_step!(None); - tracing_event!(super::tracing::Event::EndVM { - status: super::ExitStatus::Stop - }); - - let returned = self.join(); + let mut returned = self.join(); match returned.reason { Reason::Call => { self.memory.write_range(&self.return_range, &[])?; @@ -1345,6 +1528,10 @@ impl Machine { } } + unsafe { + ManuallyDrop::drop(&mut returned); + } + Ok(Action::Continue) } } diff --git a/evm_loader/program/src/evm/opcode_table.rs b/evm_loader/program/src/evm/opcode_table.rs index ab68d71fd..f74e05cb6 100644 --- a/evm_loader/program/src/evm/opcode_table.rs +++ b/evm_loader/program/src/evm/opcode_table.rs @@ -1,171 +1,234 @@ -#![allow(clippy::type_complexity)] - use crate::error::Result; +use crate::evm::tracing::EventListener; use super::{database::Database, opcode::Action, Machine}; -impl Machine { - pub const OPCODES: [fn(&mut Self, &mut B) -> Result; 256] = { - let mut opcodes: [fn(&mut Self, &mut B) -> Result; 256] = - [Self::opcode_unknown; 256]; - - opcodes[0x00] = Self::opcode_stop; - opcodes[0x01] = Self::opcode_add; - opcodes[0x02] = Self::opcode_mul; - opcodes[0x03] = Self::opcode_sub; - opcodes[0x04] = Self::opcode_div; - opcodes[0x05] = Self::opcode_sdiv; - opcodes[0x06] = Self::opcode_mod; - opcodes[0x07] = Self::opcode_smod; - opcodes[0x08] = Self::opcode_addmod; - opcodes[0x09] = Self::opcode_mulmod; - opcodes[0x0A] = Self::opcode_exp; - opcodes[0x0B] = Self::opcode_signextend; - - opcodes[0x10] = Self::opcode_lt; - opcodes[0x11] = Self::opcode_gt; - opcodes[0x12] = Self::opcode_slt; - opcodes[0x13] = Self::opcode_sgt; - opcodes[0x14] = Self::opcode_eq; - opcodes[0x15] = Self::opcode_iszero; - opcodes[0x16] = Self::opcode_and; - opcodes[0x17] = Self::opcode_or; - opcodes[0x18] = Self::opcode_xor; - opcodes[0x19] = Self::opcode_not; - opcodes[0x1A] = Self::opcode_byte; - opcodes[0x1B] = Self::opcode_shl; - opcodes[0x1C] = Self::opcode_shr; - opcodes[0x1D] = Self::opcode_sar; - - opcodes[0x20] = Self::opcode_sha3; - - opcodes[0x30] = Self::opcode_address; - opcodes[0x31] = Self::opcode_balance; - opcodes[0x32] = Self::opcode_origin; - opcodes[0x33] = Self::opcode_caller; - opcodes[0x34] = Self::opcode_callvalue; - opcodes[0x35] = Self::opcode_calldataload; - opcodes[0x36] = Self::opcode_calldatasize; - opcodes[0x37] = Self::opcode_calldatacopy; - opcodes[0x38] = Self::opcode_codesize; - opcodes[0x39] = Self::opcode_codecopy; - opcodes[0x3A] = Self::opcode_gasprice; - opcodes[0x3B] = Self::opcode_extcodesize; - opcodes[0x3C] = Self::opcode_extcodecopy; - opcodes[0x3D] = Self::opcode_returndatasize; - opcodes[0x3E] = Self::opcode_returndatacopy; - opcodes[0x3F] = Self::opcode_extcodehash; - opcodes[0x40] = Self::opcode_blockhash; - opcodes[0x41] = Self::opcode_coinbase; - opcodes[0x42] = Self::opcode_timestamp; - opcodes[0x43] = Self::opcode_number; - opcodes[0x44] = Self::opcode_difficulty; - opcodes[0x45] = Self::opcode_gaslimit; - opcodes[0x46] = Self::opcode_chainid; - opcodes[0x47] = Self::opcode_selfbalance; - opcodes[0x48] = Self::opcode_basefee; - - opcodes[0x50] = Self::opcode_pop; - opcodes[0x51] = Self::opcode_mload; - opcodes[0x52] = Self::opcode_mstore; - opcodes[0x53] = Self::opcode_mstore8; - opcodes[0x54] = Self::opcode_sload; - opcodes[0x55] = Self::opcode_sstore; - opcodes[0x56] = Self::opcode_jump; - opcodes[0x57] = Self::opcode_jumpi; - opcodes[0x58] = Self::opcode_pc; - opcodes[0x59] = Self::opcode_msize; - opcodes[0x5A] = Self::opcode_gas; - opcodes[0x5B] = Self::opcode_jumpdest; - - opcodes[0x5F] = Self::opcode_push_0; - opcodes[0x60] = Self::opcode_push_1; - opcodes[0x61] = Self::opcode_push_2_31::<2>; - opcodes[0x62] = Self::opcode_push_2_31::<3>; - opcodes[0x63] = Self::opcode_push_2_31::<4>; - opcodes[0x64] = Self::opcode_push_2_31::<5>; - opcodes[0x65] = Self::opcode_push_2_31::<6>; - opcodes[0x66] = Self::opcode_push_2_31::<7>; - opcodes[0x67] = Self::opcode_push_2_31::<8>; - opcodes[0x68] = Self::opcode_push_2_31::<9>; - opcodes[0x69] = Self::opcode_push_2_31::<10>; - opcodes[0x6A] = Self::opcode_push_2_31::<11>; - opcodes[0x6B] = Self::opcode_push_2_31::<12>; - opcodes[0x6C] = Self::opcode_push_2_31::<13>; - opcodes[0x6D] = Self::opcode_push_2_31::<14>; - opcodes[0x6E] = Self::opcode_push_2_31::<15>; - opcodes[0x6F] = Self::opcode_push_2_31::<16>; - opcodes[0x70] = Self::opcode_push_2_31::<17>; - opcodes[0x71] = Self::opcode_push_2_31::<18>; - opcodes[0x72] = Self::opcode_push_2_31::<19>; - opcodes[0x73] = Self::opcode_push_2_31::<20>; - opcodes[0x74] = Self::opcode_push_2_31::<21>; - opcodes[0x75] = Self::opcode_push_2_31::<22>; - opcodes[0x76] = Self::opcode_push_2_31::<23>; - opcodes[0x77] = Self::opcode_push_2_31::<24>; - opcodes[0x78] = Self::opcode_push_2_31::<25>; - opcodes[0x79] = Self::opcode_push_2_31::<26>; - opcodes[0x7A] = Self::opcode_push_2_31::<27>; - opcodes[0x7B] = Self::opcode_push_2_31::<28>; - opcodes[0x7C] = Self::opcode_push_2_31::<29>; - opcodes[0x7D] = Self::opcode_push_2_31::<30>; - opcodes[0x7E] = Self::opcode_push_2_31::<31>; - opcodes[0x7F] = Self::opcode_push_32; - - opcodes[0x80] = Self::opcode_dup_1_16::<1>; - opcodes[0x81] = Self::opcode_dup_1_16::<2>; - opcodes[0x82] = Self::opcode_dup_1_16::<3>; - opcodes[0x83] = Self::opcode_dup_1_16::<4>; - opcodes[0x84] = Self::opcode_dup_1_16::<5>; - opcodes[0x85] = Self::opcode_dup_1_16::<6>; - opcodes[0x86] = Self::opcode_dup_1_16::<7>; - opcodes[0x87] = Self::opcode_dup_1_16::<8>; - opcodes[0x88] = Self::opcode_dup_1_16::<9>; - opcodes[0x89] = Self::opcode_dup_1_16::<10>; - opcodes[0x8A] = Self::opcode_dup_1_16::<11>; - opcodes[0x8B] = Self::opcode_dup_1_16::<12>; - opcodes[0x8C] = Self::opcode_dup_1_16::<13>; - opcodes[0x8D] = Self::opcode_dup_1_16::<14>; - opcodes[0x8E] = Self::opcode_dup_1_16::<15>; - opcodes[0x8F] = Self::opcode_dup_1_16::<16>; - - opcodes[0x90] = Self::opcode_swap_1_16::<1>; - opcodes[0x91] = Self::opcode_swap_1_16::<2>; - opcodes[0x92] = Self::opcode_swap_1_16::<3>; - opcodes[0x93] = Self::opcode_swap_1_16::<4>; - opcodes[0x94] = Self::opcode_swap_1_16::<5>; - opcodes[0x95] = Self::opcode_swap_1_16::<6>; - opcodes[0x96] = Self::opcode_swap_1_16::<7>; - opcodes[0x97] = Self::opcode_swap_1_16::<8>; - opcodes[0x98] = Self::opcode_swap_1_16::<9>; - opcodes[0x99] = Self::opcode_swap_1_16::<10>; - opcodes[0x9A] = Self::opcode_swap_1_16::<11>; - opcodes[0x9B] = Self::opcode_swap_1_16::<12>; - opcodes[0x9C] = Self::opcode_swap_1_16::<13>; - opcodes[0x9D] = Self::opcode_swap_1_16::<14>; - opcodes[0x9E] = Self::opcode_swap_1_16::<15>; - opcodes[0x9F] = Self::opcode_swap_1_16::<16>; - - opcodes[0xA0] = Self::opcode_log_0_4::<0>; - opcodes[0xA1] = Self::opcode_log_0_4::<1>; - opcodes[0xA2] = Self::opcode_log_0_4::<2>; - opcodes[0xA3] = Self::opcode_log_0_4::<3>; - opcodes[0xA4] = Self::opcode_log_0_4::<4>; - - opcodes[0xF0] = Self::opcode_create; - opcodes[0xF1] = Self::opcode_call; - opcodes[0xF2] = Self::opcode_callcode; - opcodes[0xF3] = Self::opcode_return; - opcodes[0xF4] = Self::opcode_delegatecall; - opcodes[0xF5] = Self::opcode_create2; - - opcodes[0xFA] = Self::opcode_staticcall; - - opcodes[0xFD] = Self::opcode_revert; - opcodes[0xFE] = Self::opcode_invalid; - - opcodes[0xFF] = Self::opcode_selfdestruct; - - opcodes - }; +macro_rules! opcode_table { + ($($opcode:literal, $opname:ident, $op:path;)*) => { + #[cfg(target_os = "solana")] + type OpCode = fn(&mut Machine, &mut B) -> Result; + + #[cfg(target_os = "solana")] + impl Machine { + const OPCODES: [OpCode; 256] = { + let mut opcodes: [OpCode; 256] = [Self::opcode_unknown; 256]; + + $(opcodes[$opcode as usize] = $op;)* + + opcodes + }; + + pub fn execute_opcode(&mut self, backend: &mut B, opcode: u8) -> Result { + // SAFETY: OPCODES.len() == 256, opcode <= 255 + let opcode_fn = unsafe { Self::OPCODES.get_unchecked(opcode as usize) }; + opcode_fn(self, backend) + } + } + + #[cfg(not(target_os = "solana"))] + impl Machine { + pub async fn execute_opcode(&mut self, backend: &mut B, opcode: u8) -> Result { + match opcode { + $($opcode => $op(self, backend).await,)* + _ => Self::opcode_unknown(self, backend).await, + } + } + } + + #[cfg(not(target_os = "solana"))] + pub const OPNAMES: [&str; 256] = { + let mut opnames: [&str; 256] = [""; 256]; + + $(opnames[$opcode as usize] = stringify!($opname);)* + + opnames + }; + + #[repr(transparent)] + #[derive(PartialEq, Eq, Default)] + pub struct Opcode(pub u8); + + #[cfg(not(target_os = "solana"))] + $(pub const $opname: Opcode = Opcode($opcode);)* + + #[cfg(not(target_os = "solana"))] + impl From for Opcode { + fn from(opcode: u8) -> Self { + Opcode(opcode) + } + } + + #[cfg(not(target_os = "solana"))] + impl serde::Serialize for Opcode { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + serializer.serialize_str(OPNAMES[self.0 as usize]) + } + } + } } + +opcode_table![ + 0x00, STOP, Self::opcode_stop; + 0x01, ADD, Self::opcode_add; + 0x02, MUL, Self::opcode_mul; + 0x03, SUB, Self::opcode_sub; + 0x04, DIV, Self::opcode_div; + 0x05, SDIV, Self::opcode_sdiv; + 0x06, MOD, Self::opcode_mod; + 0x07, SMOD, Self::opcode_smod; + 0x08, ADDMOD, Self::opcode_addmod; + 0x09, MULMOD, Self::opcode_mulmod; + 0x0A, EXP, Self::opcode_exp; + 0x0B, SIGNEXTEND, Self::opcode_signextend; + + 0x10, LT, Self::opcode_lt; + 0x11, GT, Self::opcode_gt; + 0x12, SLT, Self::opcode_slt; + 0x13, SGT, Self::opcode_sgt; + 0x14, EQ, Self::opcode_eq; + 0x15, ISZERO, Self::opcode_iszero; + 0x16, AND, Self::opcode_and; + 0x17, OR, Self::opcode_or; + 0x18, XOR, Self::opcode_xor; + 0x19, NOT, Self::opcode_not; + 0x1A, BYTE, Self::opcode_byte; + 0x1B, SHL, Self::opcode_shl; + 0x1C, SHR, Self::opcode_shr; + 0x1D, SAR, Self::opcode_sar; + + 0x20, KECCAK256, Self::opcode_sha3; + + 0x30, ADDRESS, Self::opcode_address; + 0x31, BALANCE, Self::opcode_balance; + 0x32, ORIGIN, Self::opcode_origin; + 0x33, CALLER, Self::opcode_caller; + 0x34, CALLVALUE, Self::opcode_callvalue; + 0x35, CALLDATALOAD, Self::opcode_calldataload; + 0x36, CALLDATASIZE, Self::opcode_calldatasize; + 0x37, CALLDATACOPY, Self::opcode_calldatacopy; + 0x38, CODESIZE, Self::opcode_codesize; + 0x39, CODECOPY, Self::opcode_codecopy; + 0x3A, GASPRICE, Self::opcode_gasprice; + 0x3B, EXTCODESIZE, Self::opcode_extcodesize; + 0x3C, EXTCODECOPY, Self::opcode_extcodecopy; + 0x3D, RETURNDATASIZE, Self::opcode_returndatasize; + 0x3E, RETURNDATACOPY, Self::opcode_returndatacopy; + 0x3F, EXTCODEHASH, Self::opcode_extcodehash; + 0x40, BLOCKHASH, Self::opcode_blockhash; + 0x41, COINBASE, Self::opcode_coinbase; + 0x42, TIMESTAMP, Self::opcode_timestamp; + 0x43, NUMBER, Self::opcode_number; + 0x44, PREVRANDAO, Self::opcode_difficulty; + 0x45, GASLIMIT, Self::opcode_gaslimit; + 0x46, CHAINID, Self::opcode_chainid; + 0x47, SELFBALANCE, Self::opcode_selfbalance; + 0x48, BASEFEE, Self::opcode_basefee; + + 0x50, POP, Self::opcode_pop; + 0x51, MLOAD, Self::opcode_mload; + 0x52, MSTORE, Self::opcode_mstore; + 0x53, MSTORE8, Self::opcode_mstore8; + 0x54, SLOAD, Self::opcode_sload; + 0x55, SSTORE, Self::opcode_sstore; + 0x56, JUMP, Self::opcode_jump; + 0x57, JUMPI, Self::opcode_jumpi; + 0x58, PC, Self::opcode_pc; + 0x59, MSIZE, Self::opcode_msize; + 0x5A, GAS, Self::opcode_gas; + 0x5B, JUMPDEST, Self::opcode_jumpdest; + + 0x5C, TLOAD, Self::opcode_tload; + 0x5D, TSTORE, Self::opcode_tstore; + 0x5E, MCOPY, Self::opcode_mcopy; + + 0x5F, PUSH0, Self::opcode_push_0; + 0x60, PUSH1, Self::opcode_push_1; + 0x61, PUSH2, Self::opcode_push_2_31::<2>; + 0x62, PUSH3, Self::opcode_push_2_31::<3>; + 0x63, PUSH4, Self::opcode_push_2_31::<4>; + 0x64, PUSH5, Self::opcode_push_2_31::<5>; + 0x65, PUSH6, Self::opcode_push_2_31::<6>; + 0x66, PUSH7, Self::opcode_push_2_31::<7>; + 0x67, PUSH8, Self::opcode_push_2_31::<8>; + 0x68, PUSH9, Self::opcode_push_2_31::<9>; + 0x69, PUSH10, Self::opcode_push_2_31::<10>; + 0x6A, PUSH11, Self::opcode_push_2_31::<11>; + 0x6B, PUSH12, Self::opcode_push_2_31::<12>; + 0x6C, PUSH13, Self::opcode_push_2_31::<13>; + 0x6D, PUSH14, Self::opcode_push_2_31::<14>; + 0x6E, PUSH15, Self::opcode_push_2_31::<15>; + 0x6F, PUSH16, Self::opcode_push_2_31::<16>; + 0x70, PUSH17, Self::opcode_push_2_31::<17>; + 0x71, PUSH18, Self::opcode_push_2_31::<18>; + 0x72, PUSH19, Self::opcode_push_2_31::<19>; + 0x73, PUSH20, Self::opcode_push_2_31::<20>; + 0x74, PUSH21, Self::opcode_push_2_31::<21>; + 0x75, PUSH22, Self::opcode_push_2_31::<22>; + 0x76, PUSH23, Self::opcode_push_2_31::<23>; + 0x77, PUSH24, Self::opcode_push_2_31::<24>; + 0x78, PUSH25, Self::opcode_push_2_31::<25>; + 0x79, PUSH26, Self::opcode_push_2_31::<26>; + 0x7A, PUSH27, Self::opcode_push_2_31::<27>; + 0x7B, PUSH28, Self::opcode_push_2_31::<28>; + 0x7C, PUSH29, Self::opcode_push_2_31::<29>; + 0x7D, PUSH30, Self::opcode_push_2_31::<30>; + 0x7E, PUSH31, Self::opcode_push_2_31::<31>; + 0x7F, PUSH32, Self::opcode_push_32; + + 0x80, DUP1, Self::opcode_dup_1_16::<1>; + 0x81, DUP2, Self::opcode_dup_1_16::<2>; + 0x82, DUP3, Self::opcode_dup_1_16::<3>; + 0x83, DUP4, Self::opcode_dup_1_16::<4>; + 0x84, DUP5, Self::opcode_dup_1_16::<5>; + 0x85, DUP6, Self::opcode_dup_1_16::<6>; + 0x86, DUP7, Self::opcode_dup_1_16::<7>; + 0x87, DUP8, Self::opcode_dup_1_16::<8>; + 0x88, DUP9, Self::opcode_dup_1_16::<9>; + 0x89, DUP10, Self::opcode_dup_1_16::<10>; + 0x8A, DUP11, Self::opcode_dup_1_16::<11>; + 0x8B, DUP12, Self::opcode_dup_1_16::<12>; + 0x8C, DUP13, Self::opcode_dup_1_16::<13>; + 0x8D, DUP14, Self::opcode_dup_1_16::<14>; + 0x8E, DUP15, Self::opcode_dup_1_16::<15>; + 0x8F, DUP16, Self::opcode_dup_1_16::<16>; + + 0x90, SWAP1, Self::opcode_swap_1_16::<1>; + 0x91, SWAP2, Self::opcode_swap_1_16::<2>; + 0x92, SWAP3, Self::opcode_swap_1_16::<3>; + 0x93, SWAP4, Self::opcode_swap_1_16::<4>; + 0x94, SWAP5, Self::opcode_swap_1_16::<5>; + 0x95, SWAP6, Self::opcode_swap_1_16::<6>; + 0x96, SWAP7, Self::opcode_swap_1_16::<7>; + 0x97, SWAP8, Self::opcode_swap_1_16::<8>; + 0x98, SWAP9, Self::opcode_swap_1_16::<9>; + 0x99, SWAP10, Self::opcode_swap_1_16::<10>; + 0x9A, SWAP11, Self::opcode_swap_1_16::<11>; + 0x9B, SWAP12, Self::opcode_swap_1_16::<12>; + 0x9C, SWAP13, Self::opcode_swap_1_16::<13>; + 0x9D, SWAP14, Self::opcode_swap_1_16::<14>; + 0x9E, SWAP15, Self::opcode_swap_1_16::<15>; + 0x9F, SWAP16, Self::opcode_swap_1_16::<16>; + + 0xA0, LOG0, Self::opcode_log_0_4::<0>; + 0xA1, LOG1, Self::opcode_log_0_4::<1>; + 0xA2, LOG2, Self::opcode_log_0_4::<2>; + 0xA3, LOG3, Self::opcode_log_0_4::<3>; + 0xA4, LOG4, Self::opcode_log_0_4::<4>; + + 0xF0, CREATE, Self::opcode_create; + 0xF1, CALL, Self::opcode_call; + 0xF2, CALLCODE, Self::opcode_callcode; + 0xF3, RETURN, Self::opcode_return; + 0xF4, DELEGATECALL, Self::opcode_delegatecall; + 0xF5, CREATE2, Self::opcode_create2; + + 0xFA, STATICCALL, Self::opcode_staticcall; + + 0xFD, REVERT, Self::opcode_revert; + 0xFE, INVALID, Self::opcode_invalid; + + 0xFF, SENDALL, Self::opcode_sendall; +]; diff --git a/evm_loader/program/src/evm/precompile/big_mod_exp.rs b/evm_loader/program/src/evm/precompile/big_mod_exp.rs index dc92fd262..1309088c9 100644 --- a/evm_loader/program/src/evm/precompile/big_mod_exp.rs +++ b/evm_loader/program/src/evm/precompile/big_mod_exp.rs @@ -1,38 +1,36 @@ -// use ethnum::U256; +use ethnum::U256; -#[must_use] -pub fn big_mod_exp(_input: &[u8]) -> Vec { - // Should be implemented via Solana syscall - Vec::new() +use crate::types::vector::VectorVecExt; +use crate::types::Vector; +use crate::vector; - // if input.len() < 96 { - // return vec![]; - // } +#[must_use] +pub fn big_mod_exp(input: &[u8]) -> Vector { + if input.len() < 96 { + return vector![]; + } - // let (base_len, rest) = input.split_at(32); - // let (exp_len, rest) = rest.split_at(32); - // let (mod_len, rest) = rest.split_at(32); + let (base_len, rest) = input.split_at(32); + let (exp_len, rest) = rest.split_at(32); + let (mod_len, rest) = rest.split_at(32); - // let base_len = match U256::from_be_bytes(base_len.try_into().unwrap()).try_into() { - // Ok(value) => value, - // Err(_) => return vec![] - // }; - // let exp_len = match U256::from_be_bytes(exp_len.try_into().unwrap()).try_into() { - // Ok(value) => value, - // Err(_) => return vec![] - // }; - // let mod_len = match U256::from_be_bytes(mod_len.try_into().unwrap()).try_into() { - // Ok(value) => value, - // Err(_) => return vec![] - // }; + let Ok(base_len) = U256::from_be_bytes(base_len.try_into().unwrap()).try_into() else { + return vector![]; + }; + let Ok(exp_len) = U256::from_be_bytes(exp_len.try_into().unwrap()).try_into() else { + return vector![]; + }; + let Ok(mod_len) = U256::from_be_bytes(mod_len.try_into().unwrap()).try_into() else { + return vector![]; + }; - // if base_len == 0 && mod_len == 0 { - // return vec![0; 32]; - // } + if base_len == 0 && mod_len == 0 { + return vector![0; 32]; + } - // let (base_val, rest) = rest.split_at(base_len); - // let (exp_val, rest) = rest.split_at(exp_len); - // let (mod_val, _) = rest.split_at(mod_len); + let (base_val, rest) = rest.split_at(base_len); + let (exp_val, rest) = rest.split_at(exp_len); + let (mod_val, _) = rest.split_at(mod_len); - // solana_program::big_mod_exp::big_mod_exp(base_val, exp_val, mod_val) + solana_program::big_mod_exp::big_mod_exp(base_val, exp_val, mod_val).into_vector() } diff --git a/evm_loader/program/src/evm/precompile/blake2_f.rs b/evm_loader/program/src/evm/precompile/blake2_f.rs index 4c984cdcb..b8258d1c5 100644 --- a/evm_loader/program/src/evm/precompile/blake2_f.rs +++ b/evm_loader/program/src/evm/precompile/blake2_f.rs @@ -1,6 +1,10 @@ +use crate::types::vector::VectorSliceExt; +use crate::types::Vector; +use crate::vector; + #[must_use] #[allow(clippy::too_many_lines)] -pub fn blake2_f(input: &[u8]) -> Vec { +pub fn blake2_f(input: &[u8]) -> Vector { const BLAKE2_F_ARG_LEN: usize = 213; debug_print!("blake2F"); @@ -67,7 +71,7 @@ pub fn blake2_f(input: &[u8]) -> Vec { if input.len() != BLAKE2_F_ARG_LEN { // return Err(ExitError::Other("input length for Blake2 F precompile should be exactly 213 bytes".into())); - return Vec::new(); + return vector![]; } let mut rounds_arr: [u8; 4] = Default::default(); @@ -113,7 +117,7 @@ pub fn blake2_f(input: &[u8]) -> Vec { false } else { // return Err(ExitError::Other("incorrect final block indicator flag".into())) - return Vec::new(); + return vector![]; }; compress(&mut h, m, [t_0, t_1], f, rounds as usize); @@ -123,5 +127,5 @@ pub fn blake2_f(input: &[u8]) -> Vec { output_buf[i * 8..(i + 1) * 8].copy_from_slice(&state_word.to_le_bytes()); } - output_buf.to_vec() + output_buf.to_vector() } diff --git a/evm_loader/program/src/evm/precompile/bn256.rs b/evm_loader/program/src/evm/precompile/bn256.rs index 02d062907..22ba2ffe5 100644 --- a/evm_loader/program/src/evm/precompile/bn256.rs +++ b/evm_loader/program/src/evm/precompile/bn256.rs @@ -1,26 +1,47 @@ -// Should be implemented via Solana syscall -// use solana_program::alt_bn128::prelude::*; +use crate::types::vector::{VectorSliceExt, VectorVecExt}; +use crate::types::Vector; +use crate::vector; +use ethnum::U256; +use solana_program::alt_bn128::prelude::*; /// Call inner `bn256Add` #[must_use] -pub fn bn256_add(_input: &[u8]) -> Vec { - // Should be implemented via Solana syscall - Vec::new() - //alt_bn128_addition(input).unwrap_or_else(|_| vec![]) +pub fn bn256_add(input: &[u8]) -> Vector { + if input.len() >= ALT_BN128_ADDITION_INPUT_LEN { + alt_bn128_addition(&input[..ALT_BN128_ADDITION_INPUT_LEN]) + } else { + let mut buffer = vector![0_u8; ALT_BN128_ADDITION_INPUT_LEN]; + buffer[..input.len()].copy_from_slice(input); + alt_bn128_addition(&buffer) + } + .unwrap() + .into_vector() } /// Call inner `bn256ScalarMul` #[must_use] -pub fn bn256_scalar_mul(_input: &[u8]) -> Vec { - // Should be implemented via Solana syscall - Vec::new() - //alt_bn128_multiplication(input).unwrap_or_else(|_| vec![]) +pub fn bn256_scalar_mul(input: &[u8]) -> Vector { + if input.len() >= ALT_BN128_MULTIPLICATION_INPUT_LEN { + alt_bn128_multiplication(&input[..ALT_BN128_MULTIPLICATION_INPUT_LEN]) + } else { + let mut buffer = vec![0_u8; ALT_BN128_MULTIPLICATION_INPUT_LEN]; + buffer[..input.len()].copy_from_slice(input); + alt_bn128_multiplication(&buffer) + } + .unwrap() + .into_vector() } /// Call inner `bn256Pairing` #[must_use] -pub fn bn256_pairing(_input: &[u8]) -> Vec { - // Should be implemented via Solana syscall - Vec::new() - //alt_bn128_pairing(input).unwrap_or_else(|_| vec![]) +pub fn bn256_pairing(input: &[u8]) -> Vector { + if input.is_empty() { + return U256::ONE.to_be_bytes().to_vector(); + } + + if (input.len() % ALT_BN128_PAIRING_ELEMENT_LEN) != 0 { + return U256::ZERO.to_be_bytes().to_vector(); + } + + alt_bn128_pairing(input).unwrap().into_vector() } diff --git a/evm_loader/program/src/evm/precompile/datacopy.rs b/evm_loader/program/src/evm/precompile/datacopy.rs index 37e3c8715..ca61ee455 100644 --- a/evm_loader/program/src/evm/precompile/datacopy.rs +++ b/evm_loader/program/src/evm/precompile/datacopy.rs @@ -1,6 +1,9 @@ +use crate::types::vector::VectorSliceExt; +use crate::types::Vector; + #[must_use] -pub fn datacopy(input: &[u8]) -> Vec { +pub fn datacopy(input: &[u8]) -> Vector { debug_print!("datacopy"); - input.to_vec() + input.to_vector() } diff --git a/evm_loader/program/src/evm/precompile/ecrecover.rs b/evm_loader/program/src/evm/precompile/ecrecover.rs index 6bebfd162..6d5dda6a3 100644 --- a/evm_loader/program/src/evm/precompile/ecrecover.rs +++ b/evm_loader/program/src/evm/precompile/ecrecover.rs @@ -1,10 +1,14 @@ +use crate::types::vector::VectorSliceExt; +use crate::types::Vector; +use crate::vector; use arrayref::{array_ref, array_refs}; use ethnum::U256; use solana_program::keccak; use solana_program::secp256k1_recover::secp256k1_recover; +#[allow(clippy::manual_let_else)] #[must_use] -pub fn ecrecover(input: &[u8]) -> Vec { +pub fn ecrecover(input: &[u8]) -> Vector { debug_print!("ecrecover"); let input = if input.len() >= 128 { @@ -20,19 +24,18 @@ pub fn ecrecover(input: &[u8]) -> Vec { let v = U256::from_be_bytes(*v); if !(27..=30).contains(&v) { - return vec![]; + return vector![]; } let recovery_id = v.as_u8() - 27; - let public_key = match secp256k1_recover(&msg[..], recovery_id, &sig[..]) { - Ok(key) => key, - Err(_) => return vec![], + let Ok(public_key) = secp256k1_recover(&msg[..], recovery_id, &sig[..]) else { + return vector![]; }; let mut address = keccak::hash(&public_key.to_bytes()).to_bytes(); address[0..12].fill(0); debug_print!("{}", hex::encode(address)); - address.to_vec() + address.to_vector() } diff --git a/evm_loader/program/src/evm/precompile/mod.rs b/evm_loader/program/src/evm/precompile/mod.rs index 511bb903f..f75881b81 100644 --- a/evm_loader/program/src/evm/precompile/mod.rs +++ b/evm_loader/program/src/evm/precompile/mod.rs @@ -1,5 +1,6 @@ +use crate::evm::tracing::EventListener; use crate::evm::{database::Database, Machine}; -use crate::types::Address; +use crate::types::{Address, Vector}; mod big_mod_exp; mod blake2_f; @@ -56,26 +57,20 @@ pub fn is_precompile_address(address: &Address) -> bool { || *address == SYSTEM_ACCOUNT_BLAKE2F } -/// Run precompile code -#[must_use] -pub fn precompile(address: &Address, data: &[u8]) -> Option> { - match *address { - SYSTEM_ACCOUNT_ECRECOVER => Some(ecrecover::ecrecover(data)), - SYSTEM_ACCOUNT_SHA_256 => Some(sha256::sha256(data)), - SYSTEM_ACCOUNT_RIPEMD160 => Some(ripemd160::ripemd160(data)), - SYSTEM_ACCOUNT_DATACOPY => Some(datacopy::datacopy(data)), - SYSTEM_ACCOUNT_BIGMODEXP => Some(big_mod_exp::big_mod_exp(data)), - SYSTEM_ACCOUNT_BN256_ADD => Some(bn256::bn256_add(data)), - SYSTEM_ACCOUNT_BN256_SCALAR_MUL => Some(bn256::bn256_scalar_mul(data)), - SYSTEM_ACCOUNT_BN256_PAIRING => Some(bn256::bn256_pairing(data)), - SYSTEM_ACCOUNT_BLAKE2F => Some(blake2_f::blake2_f(data)), - _ => None, - } -} - -impl Machine { +impl Machine { #[must_use] - pub fn precompile(address: &Address, data: &[u8]) -> Option> { - precompile(address, data) + pub fn precompile(address: &Address, data: &[u8]) -> Option> { + match *address { + SYSTEM_ACCOUNT_ECRECOVER => Some(ecrecover::ecrecover(data)), + SYSTEM_ACCOUNT_SHA_256 => Some(sha256::sha256(data)), + SYSTEM_ACCOUNT_RIPEMD160 => Some(ripemd160::ripemd160(data)), + SYSTEM_ACCOUNT_DATACOPY => Some(datacopy::datacopy(data)), + SYSTEM_ACCOUNT_BIGMODEXP => Some(big_mod_exp::big_mod_exp(data)), + SYSTEM_ACCOUNT_BN256_ADD => Some(bn256::bn256_add(data)), + SYSTEM_ACCOUNT_BN256_SCALAR_MUL => Some(bn256::bn256_scalar_mul(data)), + SYSTEM_ACCOUNT_BN256_PAIRING => Some(bn256::bn256_pairing(data)), + SYSTEM_ACCOUNT_BLAKE2F => Some(blake2_f::blake2_f(data)), + _ => None, + } } } diff --git a/evm_loader/program/src/evm/precompile/ripemd160.rs b/evm_loader/program/src/evm/precompile/ripemd160.rs index 3c38728f0..7a718f441 100644 --- a/evm_loader/program/src/evm/precompile/ripemd160.rs +++ b/evm_loader/program/src/evm/precompile/ripemd160.rs @@ -1,5 +1,8 @@ +use crate::types::Vector; +use crate::vector; + #[must_use] -pub fn ripemd160(input: &[u8]) -> Vec { +pub fn ripemd160(input: &[u8]) -> Vector { use ripemd::{Digest, Ripemd160}; debug_print!("ripemd160"); @@ -11,7 +14,7 @@ pub fn ripemd160(input: &[u8]) -> Vec { let hash_val = hasher.finalize(); // transform to [u8; 32] - let mut result = vec![0_u8; 12]; + let mut result = vector![0_u8; 12]; result.extend(&hash_val[..]); result diff --git a/evm_loader/program/src/evm/precompile/sha256.rs b/evm_loader/program/src/evm/precompile/sha256.rs index eec4915dd..e67577343 100644 --- a/evm_loader/program/src/evm/precompile/sha256.rs +++ b/evm_loader/program/src/evm/precompile/sha256.rs @@ -1,10 +1,13 @@ +use crate::types::vector::VectorSliceExt; +use crate::types::Vector; + #[must_use] -pub fn sha256(input: &[u8]) -> Vec { +pub fn sha256(input: &[u8]) -> Vector { use solana_program::hash::hash as sha256_digest; debug_print!("sha256"); let hash = sha256_digest(input); - hash.to_bytes().to_vec() + hash.to_bytes().to_vector() } diff --git a/evm_loader/program/src/evm/stack.rs b/evm_loader/program/src/evm/stack.rs index dee7dd9fc..2dcb17fb6 100644 --- a/evm_loader/program/src/evm/stack.rs +++ b/evm_loader/program/src/evm/stack.rs @@ -1,16 +1,19 @@ #![allow(clippy::inline_always)] -use super::tracing_event; -use crate::{error::Error, types::Address}; -use ethnum::{I256, U256}; use std::{ alloc::{GlobalAlloc, Layout}, convert::TryInto, }; +use ethnum::{I256, U256}; + +use crate::allocator::acc_allocator; +use crate::{error::Error, types::Address}; + const ELEMENT_SIZE: usize = 32; const STACK_SIZE: usize = ELEMENT_SIZE * 128; +#[repr(C)] pub struct Stack { begin: *mut u8, end: *mut u8, @@ -21,7 +24,7 @@ impl Stack { pub fn new() -> Self { let (begin, end) = unsafe { let layout = Layout::from_size_align_unchecked(STACK_SIZE, ELEMENT_SIZE); - let begin = crate::allocator::EVM.alloc(layout); + let begin = acc_allocator().alloc(layout); if begin.is_null() { std::alloc::handle_alloc_error(layout); } @@ -38,7 +41,7 @@ impl Stack { } } - #[allow(dead_code)] + #[cfg(not(target_os = "solana"))] pub fn to_vec(&self) -> Vec<[u8; 32]> { let slice = unsafe { let start = self.begin.cast::<[u8; 32]>(); @@ -61,10 +64,6 @@ impl Stack { return Err(Error::StackOverflow); } - tracing_event!(super::tracing::Event::StackPush { - value: unsafe { *self.read() } - }); - unsafe { self.top = self.top.add(32); } @@ -118,7 +117,7 @@ impl Stack { } #[inline(always)] - pub fn pop_address(&mut self) -> Result<&Address, Error> { + pub fn pop_address(&mut self) -> Result { static_assertions::assert_eq_align!(Address, u8); static_assertions::assert_eq_size!(Address, [u8; 20]); @@ -126,7 +125,7 @@ impl Stack { let address = unsafe { let ptr = self.top.add(12); // discard 12 bytes - &*(ptr as *const Address) + *(ptr as *const Address) }; Ok(address) @@ -262,59 +261,7 @@ impl Drop for Stack { fn drop(&mut self) { unsafe { let layout = Layout::from_size_align_unchecked(STACK_SIZE, ELEMENT_SIZE); - crate::allocator::EVM.dealloc(self.begin, layout); - } - } -} - -impl serde::Serialize for Stack { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - unsafe { - let data = std::slice::from_raw_parts(self.begin, STACK_SIZE); - let offset: usize = self.top.offset_from(self.begin).try_into().unwrap(); - - serializer.serialize_bytes(&data[..offset]) + acc_allocator().dealloc(self.begin, layout); } } } - -impl<'de> serde::Deserialize<'de> for Stack { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct BytesVisitor; - - impl<'de> serde::de::Visitor<'de> for BytesVisitor { - type Value = Stack; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("EVM Stack") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - if v.len() % 32 != 0 { - return Err(E::invalid_length(v.len(), &self)); - } - - let mut stack = Stack::new(); - unsafe { - stack.top = stack.begin.add(v.len()); - - let slice = std::slice::from_raw_parts_mut(stack.begin, v.len()); - slice.copy_from_slice(v); - } - - Ok(stack) - } - } - - deserializer.deserialize_bytes(BytesVisitor) - } -} diff --git a/evm_loader/program/src/evm/tracing.rs b/evm_loader/program/src/evm/tracing.rs index 789008e54..983faeb54 100644 --- a/evm_loader/program/src/evm/tracing.rs +++ b/evm_loader/program/src/evm/tracing.rs @@ -1,57 +1,51 @@ +use maybe_async::maybe_async; + use super::{Context, ExitStatus}; -use ethnum::U256; +use crate::evm::database::Database; +use crate::evm::opcode_table::Opcode; -environmental::environmental!(listener: dyn EventListener + 'static); +pub struct NoopEventListener; +#[maybe_async(?Send)] pub trait EventListener { - fn enable_return_data(&self) -> bool; - fn event(&mut self, event: Event); + async fn event( + &mut self, + executor_state: &impl Database, + event: Event, + ) -> crate::error::Result<()>; +} + +#[maybe_async(?Send)] +impl EventListener for NoopEventListener { + async fn event( + &mut self, + _executor_state: &impl Database, + _event: Event, + ) -> crate::error::Result<()> { + Ok(()) + } } /// Trace event -#[derive(Debug, Clone)] pub enum Event { BeginVM { context: Context, - code: Vec, + chain_id: u64, + input: Vec, + opcode: Opcode, }, EndVM { + context: Context, + chain_id: u64, status: ExitStatus, }, BeginStep { - opcode: u8, + context: Context, + chain_id: u64, + opcode: Opcode, pc: usize, stack: Vec<[u8; 32]>, memory: Vec, + return_data: Vec, }, - EndStep { - gas_used: u64, - return_data: Option>, - }, - StackPush { - value: [u8; 32], - }, - MemorySet { - offset: usize, - data: Vec, - }, - StorageSet { - index: U256, - value: [u8; 32], - }, - StorageAccess { - index: U256, - value: [u8; 32], - }, -} - -pub fn with(f: F) { - listener::with(f); -} - -pub fn using R>( - new: &mut (dyn EventListener + 'static + Send + Sync), - f: F, -) -> R { - listener::using(new, f) } diff --git a/evm_loader/program/src/executor/action.rs b/evm_loader/program/src/executor/action.rs index 30bd51c3d..10d5c2e6a 100644 --- a/evm_loader/program/src/executor/action.rs +++ b/evm_loader/program/src/executor/action.rs @@ -1,183 +1,49 @@ +use std::fmt::Debug; + use ethnum::U256; -use serde::{Deserialize, Serialize}; use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; -use crate::types::Address; +use crate::types::{vector::Vector, Address}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[repr(C)] pub enum Action { ExternalInstruction { program_id: Pubkey, - accounts: Vec, - #[serde(with = "serde_bytes")] - data: Vec, - seeds: Vec>, + accounts: Vector, + data: Vector, + seeds: Vector>>, fee: u64, + emulated_internally: bool, }, - NeonTransfer { + Transfer { source: Address, target: Address, - #[serde(with = "serde_u256")] + chain_id: u64, value: U256, }, - NeonWithdraw { + Burn { source: Address, - #[serde(with = "serde_u256")] + chain_id: u64, value: U256, }, EvmSetStorage { address: Address, - #[serde(with = "serde_u256")] index: U256, - #[serde(with = "serde_bytes_32")] value: [u8; 32], }, - EvmIncrementNonce { + EvmSetTransientStorage { address: Address, + index: U256, + value: [u8; 32], }, - EvmSetCode { + EvmIncrementNonce { address: Address, - code: crate::evm::Buffer, + chain_id: u64, }, - EvmSelfDestruct { + EvmSetCode { address: Address, + chain_id: u64, + code: Vector, }, } - -mod serde_bytes_32 { - pub fn serialize(value: &[u8; 32], serializer: S) -> Result - where - S: serde::ser::Serializer, - { - serializer.serialize_bytes(value) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error> - where - D: serde::Deserializer<'de>, - { - struct BytesVisitor; - - impl<'de> serde::de::Visitor<'de> for BytesVisitor { - type Value = [u8; 32]; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("[u8; 32]") - } - - fn visit_bytes(self, value: &[u8]) -> Result - where - E: serde::de::Error, - { - value - .try_into() - .map_err(|_| serde::de::Error::invalid_length(value.len(), &self)) - } - - fn visit_seq(self, mut seq: S) -> Result - where - S: serde::de::SeqAccess<'de>, - { - let mut bytes = Vec::with_capacity(32); - while let Some(b) = seq.next_element()? { - bytes.push(b); - } - bytes - .try_into() - .map_err(|_| serde::de::Error::custom("Invalid [u8; 32] value")) - } - } - - deserializer.deserialize_bytes(BytesVisitor) - } -} - -mod serde_u256 { - use ethnum::U256; - use serde::{ - de::{self, SeqAccess, Visitor}, - Deserializer, Serializer, - }; - - pub fn serialize(value: &U256, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_bytes(&value.to_le_bytes()) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_bytes(U256Visitor {}) - } - - struct U256Visitor {} - - impl<'de> Visitor<'de> for U256Visitor { - type Value = U256; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("32 bytes in little endian") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: de::Error, - { - let bytes = v - .try_into() - .map_err(|_| E::invalid_length(v.len(), &self))?; - Ok(U256::from_le_bytes(bytes)) - } - - fn visit_seq(self, mut seq: S) -> Result - where - S: SeqAccess<'de>, - { - let mut bytes = Vec::with_capacity(32); - while let Some(b) = seq.next_element()? { - bytes.push(b); - } - Ok(U256::from_le_bytes( - bytes - .try_into() - .map_err(|_| de::Error::custom("Invalid U256 value"))?, - )) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn roundtrip_bincode() { - let action = Action::EvmSetStorage { - address: Address::default(), - index: U256::from_le_bytes([ - 255, 46, 185, 41, 144, 201, 3, 36, 227, 18, 148, 147, 106, 131, 110, 6, 229, 235, - 44, 154, 71, 124, 159, 144, 47, 119, 77, 5, 154, 49, 23, 54, - ]), - value: Default::default(), - }; - let serialized = bincode::serialize(&action).unwrap(); - let _deserialized: Action = bincode::deserialize(&serialized).unwrap(); - } - - #[test] - fn roundtrip_json() { - let action = Action::EvmSetStorage { - address: Address::default(), - index: U256::from_le_bytes([ - 255, 46, 185, 41, 144, 201, 3, 36, 227, 18, 148, 147, 106, 131, 110, 6, 229, 235, - 44, 154, 71, 124, 159, 144, 47, 119, 77, 5, 154, 49, 23, 54, - ]), - value: Default::default(), - }; - let serialized = serde_json::to_string(&action).unwrap(); - let _deserialized: Action = serde_json::from_str(&serialized).unwrap(); - } -} diff --git a/evm_loader/program/src/executor/cache.rs b/evm_loader/program/src/executor/cache.rs index dbc8f5f09..487dbb0b3 100644 --- a/evm_loader/program/src/executor/cache.rs +++ b/evm_loader/program/src/executor/cache.rs @@ -1,18 +1,19 @@ +use std::{cell::RefCell, rc::Rc}; + use ethnum::U256; -use serde::{Deserialize, Serialize}; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; -use std::{cell::RefCell, collections::BTreeMap, rc::Rc}; -use crate::account_storage::AccountStorage; +use crate::types::vector::VectorSliceExt; +use crate::{types::Vector, vector}; -#[derive(Serialize, Deserialize, Clone)] +#[derive(Clone)] +#[repr(C)] pub struct OwnedAccountInfo { pub key: Pubkey, pub is_signer: bool, pub is_writable: bool, pub lamports: u64, - #[serde(with = "serde_bytes")] - pub data: Vec, + pub data: Vector, pub owner: Pubkey, pub executable: bool, pub rent_epoch: solana_program::clock::Epoch, @@ -29,9 +30,9 @@ impl OwnedAccountInfo { data: if info.executable || (info.owner == program_id) { // This is only used to emulate external programs // They don't use data in our accounts - vec![] + vector![] } else { - info.data.borrow().to_vec() + info.data.borrow().to_vector() }, owner: *info.owner, executable: info.executable, @@ -55,23 +56,8 @@ impl<'a> solana_program::account_info::IntoAccountInfo<'a> for &'a mut OwnedAcco } } -#[derive(Serialize, Deserialize)] +#[repr(C)] pub struct Cache { - pub solana_accounts: BTreeMap, - #[serde(with = "ethnum::serde::bytes::le")] pub block_number: U256, - #[serde(with = "ethnum::serde::bytes::le")] pub block_timestamp: U256, } - -impl Cache { - pub fn get_account_or_insert( - &mut self, - key: Pubkey, - backend: &B, - ) -> &mut OwnedAccountInfo { - self.solana_accounts - .entry(key) - .or_insert_with(|| backend.clone_solana_account(&key)) - } -} diff --git a/evm_loader/program/src/executor/mod.rs b/evm_loader/program/src/executor/mod.rs index 722238c17..200a3d8ad 100644 --- a/evm_loader/program/src/executor/mod.rs +++ b/evm_loader/program/src/executor/mod.rs @@ -2,7 +2,10 @@ mod action; mod cache; mod precompile_extension; mod state; +mod synced_state; pub use action::Action; pub use cache::OwnedAccountInfo; pub use state::ExecutorState; +pub use state::ExecutorStateData; +pub use synced_state::SyncedExecutorState; diff --git a/evm_loader/program/src/executor/precompile_extension/call_solana.rs b/evm_loader/program/src/executor/precompile_extension/call_solana.rs new file mode 100644 index 000000000..6c6206d2c --- /dev/null +++ b/evm_loader/program/src/executor/precompile_extension/call_solana.rs @@ -0,0 +1,490 @@ +use crate::{ + account_storage::FAKE_OPERATOR, + allocator::acc_allocator, + config::ACCOUNT_SEED_VERSION, + error::{Error, Result}, + evm::database::Database, + types::{vector::VectorSliceExt, Address, Vector}, + vector, +}; + +use arrayref::array_ref; +use ethnum::U256; +use maybe_async::maybe_async; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; + +// "cfd51d32": "createResource(bytes32,uint64,uint64,bytes32)" +// "154d4aa5": "getNeonAddress(address)" +// "59e4ad63": "getResourceAddress(bytes32)" +// "4a890f31": "getSolanaPDA(bytes32,bytes)" +// "cd2d1a3a": "getExtAuthority(bytes32)" +// "30aa81c6": "getPayer()", + +// "c549a7af": "execute(uint64,bytes)", +// "32607450": "executeWithSeed(uint64,bytes32,bytes)", +// "aeed7f1e": "execute(uint64,(bytes32,(bytes32,bool,bool)[],bytes))", +// "add378af": "executeWithSeed(uint64,bytes32,(bytes32,(bytes32,bool,bool)[],bytes))", +// "cff5c1a5": "getReturnData()", + +#[maybe_async] +#[allow(clippy::too_many_lines)] +pub async fn call_solana( + state: &mut State, + address: &Address, + input: &[u8], + context: &crate::evm::Context, + is_static: bool, +) -> Result> { + if context.value != 0 { + return Err(Error::Custom("CallSolana: value != 0".to_string())); + } + + if &context.contract != address { + return Err(Error::Custom( + "CallSolana: callcode or delegatecall is not allowed".to_string(), + )); + } + + let (selector, input) = input.split_at(4); + let selector: [u8; 4] = selector.try_into()?; + + #[cfg(not(target_os = "solana"))] + log::info!("Call arguments: {}", hex::encode(input)); + + match selector { + // "c549a7af": "execute(uint64,bytes)", + [0xc5, 0x49, 0xa7, 0xaf] => { + if is_static { + return Err(Error::StaticModeViolation(*address)); + } + + let required_lamports = read_u64(&input[0..])?; + let offset = read_usize(&input[32..])?; + let instruction: Instruction = + bincode::deserialize(&input[offset + 32..]).map_err(|_| Error::OutOfBounds)?; + + let signer = context.caller; + let (_signer_pubkey, bump_seed) = state.contract_pubkey(signer); + + let signer_seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], + ]; + + execute_external_instruction( + state, + context, + instruction, + signer_seeds, + required_lamports, + ) + .await + } + + // "32607450": "executeWithSeed(uint64,bytes32,bytes)", + [0x32, 0x60, 0x74, 0x50] => { + if is_static { + return Err(Error::StaticModeViolation(*address)); + } + + let required_lamports = read_u64(&input[0..])?; + let salt = read_salt(&input[32..])?; + let offset = read_usize(&input[64..])?; + let instruction: Instruction = + bincode::deserialize(&input[offset + 32..]).map_err(|_| Error::OutOfBounds)?; + + let seeds: &[&[u8]] = &[ + &[ACCOUNT_SEED_VERSION], + b"AUTH", + context.caller.as_bytes(), + salt, + ]; + let (_, signer_seed) = Pubkey::find_program_address(seeds, state.program_id()); + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + b"AUTH".to_vector(), + context.caller.as_bytes().to_vector(), + salt.to_vector(), + vector![signer_seed], + ]; + + execute_external_instruction(state, context, instruction, seeds, required_lamports) + .await + } + + // "aeed7f1e": "execute(uint64,(bytes32,(bytes32,bool,bool)[],bytes))", + [0xae, 0xed, 0x7f, 0x1e] => { + if is_static { + return Err(Error::StaticModeViolation(*address)); + } + + let required_lamports = read_u64(&input[0..])?; + let instruction_offset = read_usize(&input[32..])?; + let instruction = read_instruction(&input[instruction_offset..])?; + + let signer = context.caller; + let (_signer_pubkey, bump_seed) = state.contract_pubkey(signer); + + let signer_seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], + ]; + + execute_external_instruction( + state, + context, + instruction, + signer_seeds, + required_lamports, + ) + .await + } + + // "add378af": "executeWithSeed(uint64,bytes32,(bytes32,(bytes32,bool,bool)[],bytes))", + [0xad, 0xd3, 0x78, 0xaf] => { + if is_static { + return Err(Error::StaticModeViolation(*address)); + } + + let required_lamports = read_u64(&input[0..])?; + let salt = read_salt(&input[32..])?; + let instruction_offset = read_usize(&input[64..])?; + let instruction = read_instruction(&input[instruction_offset..])?; + + let seeds: &[&[u8]] = &[ + &[ACCOUNT_SEED_VERSION], + b"AUTH", + context.caller.as_bytes(), + salt, + ]; + let (_, signer_seed) = Pubkey::find_program_address(seeds, state.program_id()); + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + b"AUTH".to_vector(), + context.caller.as_bytes().to_vector(), + salt.to_vector(), + vector![signer_seed], + ]; + + execute_external_instruction(state, context, instruction, seeds, required_lamports) + .await + } + + // "154d4aa5": "getNeonAddress(address)" + [0x15, 0x4d, 0x4a, 0xa5] => { + let neon_addess = Address::from(*array_ref![input, 12, 20]); + let sol_address = state.contract_pubkey(neon_addess).0; + Ok(sol_address.to_bytes().to_vector()) + } + + // "59e4ad63": "getResourceAddress(bytes32)" + [0x59, 0xe4, 0xad, 0x63] => { + let salt = read_salt(input)?; + let seeds: &[&[u8]] = &[ + &[ACCOUNT_SEED_VERSION], + b"ContractData", + context.caller.as_bytes(), + salt, + ]; + let (sol_address, _) = Pubkey::find_program_address(seeds, state.program_id()); + Ok(sol_address.to_bytes().to_vector()) + } + + // "cd2d1a3a": "getExtAuthority(bytes32)" + [0xcd, 0x2d, 0x1a, 0x3a] => { + let salt = read_salt(input)?; + let seeds: &[&[u8]] = &[ + &[ACCOUNT_SEED_VERSION], + b"AUTH", + context.caller.as_bytes(), + salt, + ]; + let (sol_address, _) = Pubkey::find_program_address(seeds, state.program_id()); + Ok(sol_address.to_bytes().to_vector()) + } + + // "4a890f31": "getSolanaPDA(bytes32,bytes)" + [0x4a, 0x89, 0x0f, 0x31] => { + let program_id = read_pubkey(&input[0..])?; + let offset = read_usize(&input[32..])?; + let length = read_usize(&input[offset..])?; + let mut seeds = Vector::with_capacity_in((length + 31) / 32, acc_allocator()); + for i in 0..length / 32 { + seeds.push(&input[offset + 32 + i * 32..offset + 32 + (i + 1) * 32]); + } + if length % 32 != 0 { + seeds.push(&input[offset + 32 + length - length % 32..offset + 32 + length]); + } + let (sol_address, _) = Pubkey::find_program_address(&seeds, &program_id); + Ok(sol_address.to_bytes().to_vector()) + } + + // "30aa81c6": "getPayer()" + [0x30, 0xaa, 0x81, 0xc6] => { + let seeds: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], b"PAYER", context.caller.as_bytes()]; + let (sol_address, _bump_seed) = Pubkey::find_program_address(seeds, state.program_id()); + + Ok(sol_address.to_bytes().to_vector()) + } + + // "cfd51d32": "createResource(bytes32,uint64,uint64,bytes32)" + [0xcf, 0xd5, 0x1d, 0x32] => { + if is_static { + return Err(Error::StaticModeViolation(*address)); + } + + let salt = read_salt(&input[0..])?; + let space = read_usize(&input[32..])?; + let _lamports = read_u64(&input[64..])?; + let owner = read_pubkey(&input[96..])?; + + let (sol_address, bump_seed) = Pubkey::find_program_address( + &[ + &[ACCOUNT_SEED_VERSION], + b"ContractData", + context.caller.as_bytes(), + salt, + ], + state.program_id(), + ); + let account = state.external_account(sol_address).await?; + let seeds: Vector> = vector![ + vector![ACCOUNT_SEED_VERSION], + b"ContractData".to_vector(), + context.caller.as_bytes().to_vector(), + salt.to_vector(), + vector![bump_seed], + ]; + + super::create_account(state, &account, space, &owner, seeds).await?; + Ok(sol_address.to_bytes().to_vector()) + } + + // "cff5c1a5": "getReturnData()", + [0xcf, 0xf5, 0xc1, 0xa5] => { + let return_value = match state.return_data() { + Some((program, data)) => { + let data_len = (data.len() + 31) & (!31); + let mut result = vector![0_u8; 32 + 32 + 32 + data_len]; + + result[0..32].copy_from_slice(&program.to_bytes()); + result[63] = 0x40; // offset - 64 bytes + + let length = U256::new(data.len() as u128); + result[64..96].copy_from_slice(&length.to_be_bytes()); + result[96..96 + data.len()].copy_from_slice(&data); + + result + } + None => { + vector![ + // program_id + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // offset of data + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + // length of data + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + } + }; + + Ok(return_value) + } + + _ => Err(Error::UnknownPrecompileMethodSelector(*address, selector)), + } +} + +#[maybe_async] +async fn execute_external_instruction( + state: &mut State, + context: &crate::evm::Context, + instruction: Instruction, + signer_seeds: Vector>, + required_lamports: u64, +) -> Result> { + #[cfg(not(target_os = "solana"))] + log::info!("instruction: {:?}", instruction); + + let called_program = instruction.program_id; + state.set_return_data(&[]); + + if called_program == *state.program_id() { + return Err(Error::RecursiveCall); + } + + for meta in &instruction.accounts { + if meta.pubkey == FAKE_OPERATOR + || meta.pubkey == state.operator() + || meta.pubkey == *state.program_id() + { + return Err(Error::InvalidAccountForCall(meta.pubkey)); + } + } + + let payer_seeds: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], b"PAYER", context.caller.as_bytes()]; + let (payer_pubkey, payer_bump_seed) = + Pubkey::find_program_address(payer_seeds, state.program_id()); + let required_payer = instruction + .accounts + .iter() + .any(|meta| meta.pubkey == payer_pubkey); + + if required_payer { + let payer_seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + b"PAYER".to_vector(), + context.caller.as_bytes().to_vector(), + vector![payer_bump_seed], + ]; + + let payer = state.external_account(payer_pubkey).await?; + if payer.lamports < required_lamports { + let transfer_instruction = solana_program::system_instruction::transfer( + &FAKE_OPERATOR, + &payer_pubkey, + required_lamports - payer.lamports, + ); + state + .queue_external_instruction(transfer_instruction, vector![], 0, false) + .await?; + } + + state + .queue_external_instruction( + instruction, + vector![signer_seeds, payer_seeds.clone()], + required_lamports, + false, + ) + .await?; + + let payer = state.external_account(payer_pubkey).await?; + if payer.lamports > 0 { + let transfer_instruction = solana_program::system_instruction::transfer( + &payer_pubkey, + &FAKE_OPERATOR, + payer.lamports, + ); + state + .queue_external_instruction(transfer_instruction, vector![payer_seeds], 0, false) + .await?; + } + } else { + state + .queue_external_instruction( + instruction, + vector![signer_seeds], + required_lamports, + false, + ) + .await?; + } + + let return_data = state + .return_data() + .and_then(|(program, data)| { + if program == called_program { + Some(data) + } else { + None + } + }) + .unwrap_or_default(); + Ok(to_solidity_bytes(&return_data)) +} + +#[inline] +fn read_instruction(input: &[u8]) -> Result { + let program_id = read_pubkey(&input[0..])?; + let accounts_offset = read_usize(&input[32..])?; + + let data_offset = read_usize(&input[64..])?; + let data_length = read_usize(&input[data_offset..])?; + + let accounts_length = read_usize(&input[accounts_offset..])?; + let mut accounts = Vec::with_capacity(accounts_length); + for i in 0..accounts_length { + let acc_offset = accounts_offset + 32 + i * 96; + let pubkey = read_pubkey(&input[acc_offset..])?; + let is_signer = read_u8(&input[acc_offset + 32..])? != 0; + let is_writable = read_u8(&input[acc_offset + 64..])? != 0; + accounts.push(AccountMeta { + pubkey, + is_signer, + is_writable, + }); + } + + Ok(Instruction { + program_id, + accounts, + data: input[data_offset + 32..data_offset + 32 + data_length].to_vec(), + }) +} + +#[inline] +fn read_u8(input: &[u8]) -> Result { + U256::from_be_bytes(*arrayref::array_ref![input, 0, 32]) + .try_into() + .map_err(Into::into) +} + +#[inline] +fn read_u64(input: &[u8]) -> Result { + U256::from_be_bytes(*arrayref::array_ref![input, 0, 32]) + .try_into() + .map_err(Into::into) +} + +#[inline] +fn read_usize(input: &[u8]) -> Result { + U256::from_be_bytes(*arrayref::array_ref![input, 0, 32]) + .try_into() + .map_err(Into::into) +} + +#[inline] +fn read_pubkey(input: &[u8]) -> Result { + if input.len() < 32 { + return Err(Error::OutOfBounds); + } + Ok(Pubkey::new_from_array(*arrayref::array_ref![input, 0, 32])) +} + +#[inline] +fn read_salt(input: &[u8]) -> Result<&[u8; 32]> { + if input.len() < 32 { + return Err(Error::OutOfBounds); + } + Ok(arrayref::array_ref![input, 0, 32]) +} + +fn to_solidity_bytes(b: &[u8]) -> Vector { + // Bytes encoding + // 32 bytes - offset + // 32 bytes - length + // length + padding bytes - data + + let data_len = (b.len() + 31) & (!31); + let mut result = vector![0_u8; 32 + 32 + data_len]; + + result[31] = 0x20; // offset - 32 bytes + + let length = U256::new(b.len() as u128); + result[32..64].copy_from_slice(&length.to_be_bytes()); + result[64..64 + b.len()].copy_from_slice(b); + + result +} diff --git a/evm_loader/program/src/executor/precompile_extension/metaplex.rs b/evm_loader/program/src/executor/precompile_extension/metaplex.rs index d0358dcd0..69c174d54 100644 --- a/evm_loader/program/src/executor/precompile_extension/metaplex.rs +++ b/evm_loader/program/src/executor/precompile_extension/metaplex.rs @@ -1,22 +1,59 @@ #![allow(clippy::unnecessary_wraps)] - use std::convert::{Into, TryInto}; use ethnum::U256; -use mpl_token_metadata::state::{ - Creator, Metadata, TokenMetadataAccount, TokenStandard, CREATE_FEE, MAX_MASTER_EDITION_LEN, - MAX_METADATA_LEN, +use maybe_async::maybe_async; +use mpl_token_metadata::{ + accounts::{MasterEdition, Metadata}, + instructions::{CreateMasterEditionV3Builder, CreateMetadataAccountV3Builder}, + programs::MPL_TOKEN_METADATA_ID, + types::{Creator, DataV2, TokenStandard}, + MAX_CREATOR_LEN, MAX_CREATOR_LIMIT, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH, MAX_URI_LENGTH, }; -use solana_program::{pubkey::Pubkey, rent::Rent, sysvar::Sysvar}; +use solana_program::pubkey::Pubkey; +use crate::types::vector::VectorSliceExt; +use crate::types::Vector; +use crate::vector; use crate::{ account::ACCOUNT_SEED_VERSION, - account_storage::AccountStorage, + account_storage::FAKE_OPERATOR, error::{Error, Result}, - executor::ExecutorState, + evm::database::Database, types::Address, }; +// TODO: Use solana-program-test in the emulator to calculate fee +// instead of relying on the hardcoded constants +const CREATE_FEE: u64 = 10_000_000; + +const MAX_DATA_SIZE: usize = 4 + + MAX_NAME_LENGTH + + 4 + + MAX_SYMBOL_LENGTH + + 4 + + MAX_URI_LENGTH + + 2 + + 1 + + 4 + + MAX_CREATOR_LIMIT * MAX_CREATOR_LEN; + +const MAX_METADATA_LEN: usize = 1 // key + + 32 // update auth pubkey + + 32 // mint pubkey + + MAX_DATA_SIZE + + 1 // primary sale + + 1 // mutable + + 9 // nonce (pretty sure this only needs to be 2) + + 2 // token standard + + 34 // collection + + 18 // uses + + 10 // collection details + + 33 // programmable config + + 75; // Padding + +pub const MAX_MASTER_EDITION_LEN: usize = 1 + 9 + 8 + 264; + // "[0xc5, 0x73, 0x50, 0xc6]": "createMetadata(bytes32,string,string,string)" // "[0x4a, 0xe8, 0xb6, 0x6b]": "createMasterEdition(bytes32,uint64)" // "[0xf7, 0xb6, 0x37, 0xbb]": "isInitialized(bytes32)" @@ -25,13 +62,14 @@ use crate::{ // "[0x69, 0x1f, 0x34, 0x31]": "name(bytes32)" // "[0x6b, 0xaa, 0x03, 0x30]": "symbol(bytes32)" -pub fn metaplex( - state: &mut ExecutorState, +#[maybe_async] +pub async fn metaplex( + state: &mut State, address: &Address, input: &[u8], context: &crate::evm::Context, is_static: bool, -) -> Result> { +) -> Result> { if context.value != 0 { return Err(Error::Custom("Metaplex: value != 0".to_string())); } @@ -57,7 +95,7 @@ pub fn metaplex( let symbol = read_string(input, 64, 256)?; let uri = read_string(input, 96, 1024)?; - create_metadata(context, state, mint, name, symbol, uri) + create_metadata(context, state, mint, name, symbol, uri).await } [0x4a, 0xe8, 0xb6, 0x6b] => { // "createMasterEdition(bytes32,uint64)" @@ -68,32 +106,32 @@ pub fn metaplex( let mint = read_pubkey(input)?; let max_supply = read_u64(&input[32..])?; - create_master_edition(context, state, mint, Some(max_supply)) + create_master_edition(context, state, mint, Some(max_supply)).await } [0xf7, 0xb6, 0x37, 0xbb] => { // "isInitialized(bytes32)" let mint = read_pubkey(input)?; - is_initialized(context, state, mint) + is_initialized(context, state, mint).await } [0x23, 0x5b, 0x2b, 0x94] => { // "isNFT(bytes32)" let mint = read_pubkey(input)?; - is_nft(context, state, mint) + is_nft(context, state, mint).await } [0x9e, 0xd1, 0x9d, 0xdb] => { // "uri(bytes32)" let mint = read_pubkey(input)?; - uri(context, state, mint) + uri(context, state, mint).await } [0x69, 0x1f, 0x34, 0x31] => { // "name(bytes32)" let mint = read_pubkey(input)?; - token_name(context, state, mint) + token_name(context, state, mint).await } [0x6b, 0xaa, 0x03, 0x30] => { // "symbol(bytes32)" let mint = read_pubkey(input)?; - symbol(context, state, mint) + symbol(context, state, mint).await } _ => Err(Error::UnknownPrecompileMethodSelector(*address, selector)), } @@ -142,116 +180,125 @@ fn read_string(input: &[u8], offset_position: usize, max_length: usize) -> Resul String::from_utf8(data).map_err(|_| Error::Custom("Invalid utf8 string".to_string())) } -fn create_metadata( +#[maybe_async] +async fn create_metadata( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, mint: Pubkey, name: String, symbol: String, uri: String, -) -> Result> { +) -> Result> { let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; - let (metadata_pubkey, _) = mpl_token_metadata::pda::find_metadata_account(&mint); - - let instruction = mpl_token_metadata::instruction::create_metadata_accounts_v3( - mpl_token_metadata::ID, - metadata_pubkey, - mint, - signer_pubkey, - *state.backend.operator(), - signer_pubkey, - name, - symbol, - uri, - Some(vec![ - Creator { - address: *state.backend.program_id(), - verified: false, - share: 0, - }, - Creator { - address: signer_pubkey, - verified: true, - share: 100, - }, - ]), - 0, // Seller Fee - true, // Update Authority == Mint Authority - false, // Is Mutable - None, // Collection - None, // Uses - None, // Collection Details - ); - - let rent = Rent::get()?; - let fee = rent.minimum_balance(MAX_METADATA_LEN) + CREATE_FEE; - - state.queue_external_instruction(instruction, seeds, fee); - - Ok(metadata_pubkey.to_bytes().to_vec()) + let (metadata_pubkey, _) = Metadata::find_pda(&mint); + + let instruction = CreateMetadataAccountV3Builder::new() + .metadata(metadata_pubkey) + .mint(mint) + .mint_authority(signer_pubkey) + .update_authority(signer_pubkey, true) + .payer(FAKE_OPERATOR) + .is_mutable(true) + .data(DataV2 { + name, + symbol, + uri, + seller_fee_basis_points: 0, + creators: Some(vec![ + Creator { + address: *state.program_id(), + verified: false, + share: 0, + }, + Creator { + address: signer_pubkey, + verified: true, + share: 100, + }, + ]), + collection: None, + uses: None, + }) + .instruction(); + + let fee = state.rent().minimum_balance(MAX_METADATA_LEN) + CREATE_FEE; + state + .queue_external_instruction(instruction, vector![seeds], fee, true) + .await?; + + Ok(metadata_pubkey.to_bytes().to_vector()) } -fn create_master_edition( +#[maybe_async] +async fn create_master_edition( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, mint: Pubkey, max_supply: Option, -) -> Result> { +) -> Result> { let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; - let (metadata_pubkey, _) = mpl_token_metadata::pda::find_metadata_account(&mint); - let (edition_pubkey, _) = mpl_token_metadata::pda::find_master_edition_account(&mint); - - let instruction = mpl_token_metadata::instruction::create_master_edition_v3( - mpl_token_metadata::ID, - edition_pubkey, - mint, - signer_pubkey, - signer_pubkey, - metadata_pubkey, - *state.backend.operator(), - max_supply, - ); + let (metadata_pubkey, _) = Metadata::find_pda(&mint); + let (edition_pubkey, _) = MasterEdition::find_pda(&mint); + + let mut instruction_builder = CreateMasterEditionV3Builder::new(); + instruction_builder + .metadata(metadata_pubkey) + .edition(edition_pubkey) + .mint(mint) + .mint_authority(signer_pubkey) + .update_authority(signer_pubkey) + .payer(FAKE_OPERATOR); + + if let Some(max_supply) = max_supply { + instruction_builder.max_supply(max_supply); + } - let rent = Rent::get()?; - let fee = rent.minimum_balance(MAX_MASTER_EDITION_LEN) + CREATE_FEE; + let instruction = instruction_builder.instruction(); - state.queue_external_instruction(instruction, seeds, fee); + let fee = state.rent().minimum_balance(MAX_MASTER_EDITION_LEN) + CREATE_FEE; + state + .queue_external_instruction(instruction, vector![seeds], fee, true) + .await?; - Ok(edition_pubkey.to_bytes().to_vec()) + Ok(edition_pubkey.to_bytes().to_vector()) } -fn is_initialized( +#[maybe_async] +async fn is_initialized( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, mint: Pubkey, -) -> Result> { - let is_initialized = metadata(context, state, mint)?.map_or_else(|| false, |_| true); +) -> Result> { + let is_initialized = metadata(context, state, mint) + .await? + .map_or_else(|| false, |_| true); Ok(to_solidity_bool(is_initialized)) } -fn is_nft( +#[maybe_async] +async fn is_nft( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, mint: Pubkey, -) -> Result> { - let is_nft = metadata(context, state, mint)?.map_or_else( +) -> Result> { + let is_nft = metadata(context, state, mint).await?.map_or_else( || false, |m| m.token_standard == Some(TokenStandard::NonFungible), ); @@ -259,46 +306,56 @@ fn is_nft( Ok(to_solidity_bool(is_nft)) } -fn uri( +#[maybe_async] +async fn uri( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, mint: Pubkey, -) -> Result> { - let uri = metadata(context, state, mint)?.map_or_else(String::new, |m| m.data.uri); +) -> Result> { + let uri = metadata(context, state, mint) + .await? + .map_or_else(String::new, |m| m.uri); Ok(to_solidity_string(uri.trim_end_matches('\0'))) } -fn token_name( +#[maybe_async] +async fn token_name( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, mint: Pubkey, -) -> Result> { - let token_name = metadata(context, state, mint)?.map_or_else(String::new, |m| m.data.name); +) -> Result> { + let token_name = metadata(context, state, mint) + .await? + .map_or_else(String::new, |m| m.name); Ok(to_solidity_string(token_name.trim_end_matches('\0'))) } -fn symbol( +#[maybe_async] +async fn symbol( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, mint: Pubkey, -) -> Result> { - let symbol = metadata(context, state, mint)?.map_or_else(String::new, |m| m.data.symbol); +) -> Result> { + let symbol = metadata(context, state, mint) + .await? + .map_or_else(String::new, |m| m.symbol); Ok(to_solidity_string(symbol.trim_end_matches('\0'))) } -fn metadata( +#[maybe_async] +async fn metadata( _context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, mint: Pubkey, ) -> Result> { - let (metadata_pubkey, _) = mpl_token_metadata::pda::find_metadata_account(&mint); - let metadata_account = state.external_account(metadata_pubkey)?; + let (metadata_pubkey, _) = Metadata::find_pda(&mint); + let metadata_account = state.external_account(metadata_pubkey).await?; let result = { - if mpl_token_metadata::check_id(&metadata_account.owner) { + if MPL_TOKEN_METADATA_ID == metadata_account.owner { let metadata = Metadata::safe_deserialize(&metadata_account.data); metadata.ok() } else { @@ -308,13 +365,13 @@ fn metadata( Ok(result) } -fn to_solidity_bool(v: bool) -> Vec { - let mut result = vec![0_u8; 32]; +fn to_solidity_bool(v: bool) -> Vector { + let mut result = vector![0_u8; 32]; result[31] = u8::from(v); result } -fn to_solidity_string(s: &str) -> Vec { +fn to_solidity_string(s: &str) -> Vector { // String encoding // 32 bytes - offset // 32 bytes - length @@ -326,7 +383,7 @@ fn to_solidity_string(s: &str) -> Vec { ((s.len() / 32) + 1) * 32 }; - let mut result = vec![0_u8; 32 + 32 + data_len]; + let mut result = vector![0_u8; 32 + 32 + data_len]; result[31] = 0x20; // offset - 32 bytes diff --git a/evm_loader/program/src/executor/precompile_extension/mod.rs b/evm_loader/program/src/executor/precompile_extension/mod.rs index 06870b608..5e20dbb6f 100644 --- a/evm_loader/program/src/executor/precompile_extension/mod.rs +++ b/evm_loader/program/src/executor/precompile_extension/mod.rs @@ -1,13 +1,25 @@ -use crate::{account_storage::AccountStorage, error::Result, evm::Context, types::Address}; +use crate::types::Vector; +use crate::vector; +use crate::{ + account_storage::FAKE_OPERATOR, + error::Result, + evm::{database::Database, Context}, + types::Address, +}; +use maybe_async::maybe_async; +use solana_program::{pubkey::Pubkey, system_instruction}; -use super::ExecutorState; +use super::OwnedAccountInfo; +mod call_solana; mod metaplex; mod neon_token; mod query_account; mod spl_token; -impl<'a, B: AccountStorage> ExecutorState<'a, B> { +pub struct PrecompiledContracts {} + +impl PrecompiledContracts { #[deprecated] const _SYSTEM_ACCOUNT_ERC20_WRAPPER: Address = Address([ 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, @@ -24,37 +36,77 @@ impl<'a, B: AccountStorage> ExecutorState<'a, B> { const SYSTEM_ACCOUNT_METAPLEX: Address = Address([ 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x05, ]); + const SYSTEM_ACCOUNT_CALL_SOLANA: Address = Address([ + 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x06, + ]); #[must_use] - #[allow(clippy::unused_self)] - pub fn is_precompile_extension(&self, address: &Address) -> bool { + pub fn is_precompile_extension(address: &Address) -> bool { *address == Self::SYSTEM_ACCOUNT_QUERY || *address == Self::SYSTEM_ACCOUNT_NEON_TOKEN || *address == Self::SYSTEM_ACCOUNT_SPL_TOKEN || *address == Self::SYSTEM_ACCOUNT_METAPLEX + || *address == Self::SYSTEM_ACCOUNT_CALL_SOLANA } - pub fn call_precompile_extension( - &mut self, + #[maybe_async] + pub async fn call_precompile_extension( + state: &mut State, context: &Context, address: &Address, input: &[u8], is_static: bool, - ) -> Option>> { + ) -> Option>> { match *address { - Self::SYSTEM_ACCOUNT_QUERY => Some(query_account::query_account( - self, address, input, context, is_static, - )), - Self::SYSTEM_ACCOUNT_NEON_TOKEN => Some(neon_token::neon_token( - self, address, input, context, is_static, - )), - Self::SYSTEM_ACCOUNT_SPL_TOKEN => Some(spl_token::spl_token( - self, address, input, context, is_static, - )), + Self::SYSTEM_ACCOUNT_QUERY => { + Some(query_account::query_account(state, address, input, context, is_static).await) + } + Self::SYSTEM_ACCOUNT_NEON_TOKEN => { + Some(neon_token::neon_token(state, address, input, context, is_static).await) + } + Self::SYSTEM_ACCOUNT_SPL_TOKEN => { + Some(spl_token::spl_token(state, address, input, context, is_static).await) + } Self::SYSTEM_ACCOUNT_METAPLEX => { - Some(metaplex::metaplex(self, address, input, context, is_static)) + Some(metaplex::metaplex(state, address, input, context, is_static).await) + } + Self::SYSTEM_ACCOUNT_CALL_SOLANA => { + Some(call_solana::call_solana(state, address, input, context, is_static).await) } _ => None, } } } + +#[maybe_async] +pub async fn create_account( + state: &mut State, + account: &OwnedAccountInfo, + space: usize, + owner: &Pubkey, + seeds: Vector>, +) -> Result<()> { + let minimum_balance = state.rent().minimum_balance(space); + + let required_lamports = minimum_balance.saturating_sub(account.lamports); + + if required_lamports > 0 { + let transfer = + system_instruction::transfer(&FAKE_OPERATOR, &account.key, required_lamports); + state + .queue_external_instruction(transfer, vector![], required_lamports, true) + .await?; + } + + let allocate = system_instruction::allocate(&account.key, space.try_into().unwrap()); + state + .queue_external_instruction(allocate, vector![seeds.clone()], 0, true) + .await?; + + let assign = system_instruction::assign(&account.key, owner); + state + .queue_external_instruction(assign, vector![seeds], 0, true) + .await?; + + Ok(()) +} diff --git a/evm_loader/program/src/executor/precompile_extension/neon_token.rs b/evm_loader/program/src/executor/precompile_extension/neon_token.rs index 0f33d5f24..93d431520 100644 --- a/evm_loader/program/src/executor/precompile_extension/neon_token.rs +++ b/evm_loader/program/src/executor/precompile_extension/neon_token.rs @@ -2,13 +2,19 @@ use std::convert::TryInto; use arrayref::array_ref; use ethnum::U256; -use solana_program::{program_pack::Pack, pubkey::Pubkey, rent::Rent, sysvar::Sysvar}; +use maybe_async::maybe_async; +use solana_program::{account_info::IntoAccountInfo, program_pack::Pack, pubkey::Pubkey}; use spl_associated_token_account::get_associated_token_address; +use crate::types::vector::VectorSliceExt; +use crate::vector; + +use crate::types::Vector; use crate::{ - account_storage::AccountStorage, + account::token, + account_storage::FAKE_OPERATOR, error::{Error, Result}, - executor::ExecutorState, + evm::database::Database, types::Address, }; @@ -18,13 +24,14 @@ use crate::{ //-------------------------------------------------- const NEON_TOKEN_METHOD_WITHDRAW_ID: &[u8; 4] = &[0x8e, 0x19, 0x89, 0x9e]; -pub fn neon_token( - state: &mut ExecutorState, +#[maybe_async] +pub async fn neon_token( + state: &mut State, address: &Address, input: &[u8], context: &crate::evm::Context, is_static: bool, -) -> Result> { +) -> Result> { debug_print!("neon_token({})", hex::encode(input)); if &context.contract != address { @@ -42,13 +49,15 @@ pub fn neon_token( } let source = context.contract; + let chain_id = context.contract_chain_id; + let value = context.value; // owner of the associated token account let destination = array_ref![rest, 0, 32]; let destination = Pubkey::new_from_array(*destination); - withdraw(state, source, destination, context.value)?; + withdraw(state, source, chain_id, destination, value).await?; - let mut output = vec![0_u8; 32]; + let mut output = vector![0_u8; 32]; output[31] = 1; // return true return Ok(output); @@ -58,9 +67,11 @@ pub fn neon_token( Err(Error::UnknownPrecompileMethodSelector(*address, *method_id)) } -fn withdraw( - state: &mut ExecutorState, +#[maybe_async] +async fn withdraw( + state: &mut State, source: Address, + chain_id: u64, target: Pubkey, value: U256, ) -> Result<()> { @@ -68,7 +79,17 @@ fn withdraw( return Err(Error::Custom("Neon Withdraw: value == 0".to_string())); } - let additional_decimals: u32 = (18 - crate::config::token_mint::decimals()).into(); + let mint_address = state.chain_id_to_token(chain_id); + + let mut mint_account = state.external_account(mint_address).await?; + let mint_data = { + let info = mint_account.into_account_info(); + token::Mint::from_account(&info)?.into_data() + }; + + assert!(mint_data.decimals < 18); + + let additional_decimals: u32 = (18 - mint_data.decimals).into(); let min_amount: u128 = u128::pow(10, additional_decimals); let spl_amount = value / min_amount; @@ -86,41 +107,39 @@ fn withdraw( ))); } - let target_token = get_associated_token_address(&target, state.backend.neon_token_mint()); - let account = state.external_account(target_token)?; + let target_token = get_associated_token_address(&target, &mint_address); + let account = state.external_account(target_token).await?; if !spl_token::check_id(&account.owner) { use spl_associated_token_account::instruction::create_associated_token_account; - let create_associated = create_associated_token_account( - state.backend.operator(), - &target, - state.backend.neon_token_mint(), - &spl_token::ID, - ); + let create_associated = + create_associated_token_account(&FAKE_OPERATOR, &target, &mint_address, &spl_token::ID); - let rent = Rent::get()?; - let fee = rent.minimum_balance(spl_token::state::Account::LEN); - state.queue_external_instruction(create_associated, vec![], fee); + let fee = state.rent().minimum_balance(spl_token::state::Account::LEN); + state + .queue_external_instruction(create_associated, vector![], fee, true) + .await?; } - let (authority, bump_seed) = - Pubkey::find_program_address(&[b"Deposit"], state.backend.program_id()); - let pool = get_associated_token_address(&authority, state.backend.neon_token_mint()); + let (authority, bump_seed) = Pubkey::find_program_address(&[b"Deposit"], state.program_id()); + let pool = get_associated_token_address(&authority, &mint_address); let transfer = spl_token::instruction::transfer_checked( &spl_token::ID, &pool, - state.backend.neon_token_mint(), + &mint_address, &target_token, &authority, &[], spl_amount.as_u64(), - crate::config::token_mint::decimals(), + mint_data.decimals, )?; - let transfer_seeds = vec![b"Deposit".to_vec(), vec![bump_seed]]; - state.queue_external_instruction(transfer, transfer_seeds, 0); + let transfer_seeds = vector![b"Deposit".to_vector(), vector![bump_seed]]; - state.withdraw_neons(source, value); + state.burn(source, chain_id, value).await?; + state + .queue_external_instruction(transfer, vector![transfer_seeds], 0, true) + .await?; Ok(()) } diff --git a/evm_loader/program/src/executor/precompile_extension/query_account.rs b/evm_loader/program/src/executor/precompile_extension/query_account.rs index 637e7d80f..00f5f5ec6 100644 --- a/evm_loader/program/src/executor/precompile_extension/query_account.rs +++ b/evm_loader/program/src/executor/precompile_extension/query_account.rs @@ -2,13 +2,14 @@ use std::convert::TryInto; use arrayref::{array_ref, array_refs}; use ethnum::U256; +use maybe_async::maybe_async; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; use crate::{ - account_storage::AccountStorage, error::{Error, Result}, - executor::ExecutorState, - types::Address, + evm::database::Database, + types::{vector::VectorSliceExt, Address, Vector}, + vector, }; // QueryAccount method DEPRECATED ids: @@ -30,13 +31,14 @@ use crate::{ // "a9dbaf25": "length(bytes32)", // "7dd6c1a0": "data(bytes32,uint64,uint64)", -pub fn query_account( - state: &mut ExecutorState, +#[maybe_async] +pub async fn query_account( + state: &State, address: &Address, input: &[u8], context: &crate::evm::Context, _is_static: bool, -) -> Result> { +) -> Result> { debug_print!("query_account({})", hex::encode(input)); if context.value != 0 { @@ -53,27 +55,27 @@ pub fn query_account( [0x2b, 0x3c, 0x83, 0x22] => { // cache(uint256,uint64,uint64) // deprecated - Ok(Vec::new()) + Ok(vector![]) } [0xa1, 0x23, 0xc3, 0x3e] | [0x02, 0x57, 0x1b, 0xe3] => { debug_print!("query_account.owner({})", &account_address); - account_owner(state, &account_address) + account_owner(state, &account_address).await } [0xaa, 0x8b, 0x99, 0xd2] | [0xa9, 0xdb, 0xaf, 0x25] => { debug_print!("query_account.length({})", &account_address); - account_data_length(state, &account_address) + account_data_length(state, &account_address).await } [0x74, 0x8f, 0x2d, 0x8a] | [0x62, 0x73, 0x44, 0x8f] => { debug_print!("query_account.lamports({})", &account_address); - account_lamports(state, &account_address) + account_lamports(state, &account_address).await } [0xc2, 0x19, 0xa7, 0x85] | [0xe6, 0xbe, 0xf4, 0x88] => { debug_print!("query_account.executable({})", &account_address); - account_is_executable(state, &account_address) + account_is_executable(state, &account_address).await } [0xc4, 0xd3, 0x69, 0xb5] | [0x8b, 0xb9, 0xe6, 0xf4] => { debug_print!("query_account.rent_epoch({})", &account_address); - account_rent_epoch(state, &account_address) + account_rent_epoch(state, &account_address).await } [0x43, 0xca, 0x51, 0x61] | [0x7d, 0xd6, 0xc1, 0xa0] => { let arguments = array_ref![rest, 0, 64]; @@ -86,11 +88,11 @@ pub fn query_account( offset, length ); - account_data(state, &account_address, offset, length) + account_data(state, &account_address, offset, length).await } [0xb6, 0x4a, 0x09, 0x7e] => { debug_print!("query_account.info({})", &account_address); - account_info(state, &account_address) + account_info(state, &account_address).await } _ => { debug_print!("query_account UNKNOWN {:?}", method_id); @@ -100,84 +102,84 @@ pub fn query_account( } #[allow(clippy::unnecessary_wraps)] -fn account_owner( - state: &mut ExecutorState, - address: &Pubkey, -) -> Result> { +#[maybe_async] +async fn account_owner(state: &State, address: &Pubkey) -> Result> { let owner = state - .backend - .map_solana_account(address, |info| info.owner.to_bytes()); + .map_solana_account(address, |info| info.owner.to_bytes()) + .await; - Ok(owner.to_vec()) + Ok(owner.to_vector()) } #[allow(clippy::unnecessary_wraps)] -fn account_lamports( - state: &mut ExecutorState, - address: &Pubkey, -) -> Result> { +#[maybe_async] +async fn account_lamports(state: &State, address: &Pubkey) -> Result> { let lamports: U256 = state - .backend .map_solana_account(address, |info| **info.lamports.borrow()) + .await .into(); - let bytes = lamports.to_be_bytes().to_vec(); + let bytes = lamports.to_be_bytes().to_vector(); Ok(bytes) } #[allow(clippy::unnecessary_wraps)] -fn account_rent_epoch( - state: &mut ExecutorState, +#[maybe_async] +async fn account_rent_epoch( + state: &State, address: &Pubkey, -) -> Result> { +) -> Result> { let epoch: U256 = state - .backend .map_solana_account(address, |info| info.rent_epoch) + .await .into(); - let bytes = epoch.to_be_bytes().to_vec(); + let bytes = epoch.to_be_bytes().to_vector(); Ok(bytes) } #[allow(clippy::unnecessary_wraps)] -fn account_is_executable( - state: &mut ExecutorState, +#[maybe_async] +async fn account_is_executable( + state: &State, address: &Pubkey, -) -> Result> { +) -> Result> { let executable: U256 = state - .backend .map_solana_account(address, |info| info.executable) + .await .into(); - let bytes = executable.to_be_bytes().to_vec(); + let bytes = executable.to_be_bytes().to_vector(); Ok(bytes) } #[allow(clippy::unnecessary_wraps)] -fn account_data_length( - state: &mut ExecutorState, +#[maybe_async] +async fn account_data_length( + state: &State, address: &Pubkey, -) -> Result> { +) -> Result> { let length: U256 = state - .backend .map_solana_account(address, |info| info.data.borrow().len()) + .await .try_into()?; - let bytes = length.to_be_bytes().to_vec(); + let bytes = length.to_be_bytes().to_vector(); Ok(bytes) } #[allow(clippy::unnecessary_wraps)] -fn account_data( - state: &mut ExecutorState, +#[maybe_async] +async fn account_data( + state: &State, address: &Pubkey, offset: usize, length: usize, -) -> Result> { +) -> Result> { if length == 0 { return Err(Error::Custom( "Query Account: data() - length == 0".to_string(), @@ -185,22 +187,20 @@ fn account_data( } state - .backend .map_solana_account(address, |info| { info.data .borrow() .get(offset..offset + length) - .map(<[u8]>::to_vec) + .map(<[u8]>::to_vector) }) + .await .ok_or_else(|| Error::Custom("Query Account: data() - out of bounds".to_string())) } #[allow(clippy::unnecessary_wraps)] -fn account_info( - state: &mut ExecutorState, - address: &Pubkey, -) -> Result> { - fn to_solidity_account_value(info: &AccountInfo) -> Vec { +#[maybe_async] +async fn account_info(state: &State, address: &Pubkey) -> Result> { + fn to_solidity_account_value(info: &AccountInfo) -> Vector { let mut buffer = [0_u8; 5 * 32]; let (key, _, lamports, owner, _, executable, _, rent_epoch) = arrayref::mut_array_refs![&mut buffer, 32, 24, 8, 32, 31, 1, 24, 8]; @@ -211,12 +211,12 @@ fn account_info( executable[0] = info.executable.into(); *rent_epoch = info.rent_epoch.to_be_bytes(); - buffer.to_vec() + buffer.to_vector() } let info = state - .backend - .map_solana_account(address, to_solidity_account_value); + .map_solana_account(address, to_solidity_account_value) + .await; Ok(info) } diff --git a/evm_loader/program/src/executor/precompile_extension/spl_token.rs b/evm_loader/program/src/executor/precompile_extension/spl_token.rs index 674f95419..499553852 100644 --- a/evm_loader/program/src/executor/precompile_extension/spl_token.rs +++ b/evm_loader/program/src/executor/precompile_extension/spl_token.rs @@ -1,19 +1,24 @@ use std::convert::{Into, TryInto}; use ethnum::U256; +use maybe_async::maybe_async; use solana_program::{ - program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, rent::Rent, - system_instruction, system_program, sysvar::Sysvar, + program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, system_program, }; +use super::create_account; use crate::{ account::ACCOUNT_SEED_VERSION, - account_storage::AccountStorage, + account_storage::FAKE_OPERATOR, error::{Error, Result}, - executor::{ExecutorState, OwnedAccountInfo}, + evm::database::Database, types::Address, }; +use crate::vector; + +use crate::types::vector::{Vector, VectorSliceExt}; + // [0xa9, 0xc1, 0x58, 0x06] : "approve(bytes32,bytes32,uint64)", // [0xc0, 0x67, 0xee, 0xbb] : "burn(bytes32,bytes32,uint64)", // [0x57, 0x82, 0xa0, 0x43] : "closeAccount(bytes32)", @@ -33,13 +38,14 @@ use crate::{ // [0x7c, 0x0e, 0xb8, 0x10] : "transferWithSeed(bytes32,bytes32,bytes32,uint64)" #[allow(clippy::too_many_lines)] -pub fn spl_token( - state: &mut ExecutorState, +#[maybe_async] +pub async fn spl_token( + state: &mut State, address: &Address, input: &[u8], context: &crate::evm::Context, is_static: bool, -) -> Result> { +) -> Result> { if context.value != 0 { return Err(Error::Custom("SplToken: value != 0".to_string())); } @@ -63,7 +69,7 @@ pub fn spl_token( let seed = read_salt(input)?; let decimals = read_u8(&input[32..])?; - initialize_mint(context, state, seed, decimals, None, None) + initialize_mint(context, state, seed, decimals, None, None).await } [0xc3, 0xf3, 0xf2, 0xf2] => { // initializeMint(bytes32 seed, uint8 decimals, bytes32 mint_authority, bytes32 freeze_authority) @@ -83,6 +89,7 @@ pub fn spl_token( Some(mint_authority), Some(freeze_authority), ) + .await } [0xda, 0xa1, 0x2c, 0x5c] => { // initializeAccount(bytes32 seed, bytes32 mint) @@ -93,7 +100,7 @@ pub fn spl_token( let seed = read_salt(input)?; let mint = read_pubkey(&input[32..])?; - initialize_account(context, state, seed, mint, None) + initialize_account(context, state, seed, mint, None).await } [0xfc, 0x86, 0xb7, 0x17] => { // initializeAccount(bytes32 seed, bytes32 mint, bytes32 owner) @@ -104,7 +111,7 @@ pub fn spl_token( let seed = read_salt(input)?; let mint = read_pubkey(&input[32..])?; let owner = read_pubkey(&input[64..])?; - initialize_account(context, state, seed, mint, Some(owner)) + initialize_account(context, state, seed, mint, Some(owner)).await } [0x57, 0x82, 0xa0, 0x43] => { // closeAccount(bytes32 account) @@ -113,7 +120,7 @@ pub fn spl_token( } let account = read_pubkey(input)?; - close_account(context, state, account) + close_account(context, state, account).await } [0xa9, 0xc1, 0x58, 0x06] => { // approve(bytes32 source, bytes32 target, uint64 amount) @@ -124,7 +131,7 @@ pub fn spl_token( let source = read_pubkey(input)?; let target = read_pubkey(&input[32..])?; let amount = read_u64(&input[64..])?; - approve(context, state, source, target, amount) + approve(context, state, source, target, amount).await } [0xb7, 0x5c, 0x7d, 0xc6] => { // revoke(bytes32 source) @@ -133,7 +140,7 @@ pub fn spl_token( } let source = read_pubkey(input)?; - revoke(context, state, source) + revoke(context, state, source).await } [0x78, 0x42, 0x3b, 0xcf] => { // transfer(bytes32 source, bytes32 target, uint64 amount) @@ -144,7 +151,7 @@ pub fn spl_token( let source = read_pubkey(input)?; let target = read_pubkey(&input[32..])?; let amount = read_u64(&input[64..])?; - transfer(context, state, source, target, amount) + transfer(context, state, source, target, amount).await } [0x7c, 0x0e, 0xb8, 0x10] => { // transferWithSeed(bytes32,bytes32,bytes32,uint64) @@ -157,7 +164,7 @@ pub fn spl_token( let target = read_pubkey(&input[64..])?; let amount = read_u64(&input[96..])?; - transfer_with_seed(context, state, seed, source, target, amount) + transfer_with_seed(context, state, seed, source, target, amount).await } [0xc9, 0xd0, 0xe2, 0xfd] => { // mintTo(bytes32 mint, bytes32 account, uint64 amount) @@ -168,7 +175,7 @@ pub fn spl_token( let mint = read_pubkey(input)?; let account = read_pubkey(&input[32..])?; let amount = read_u64(&input[64..])?; - mint_to(context, state, mint, account, amount) + mint_to(context, state, mint, account, amount).await } [0xc0, 0x67, 0xee, 0xbb] => { // burn(bytes32 mint, bytes32 account, uint64 amount) @@ -179,7 +186,7 @@ pub fn spl_token( let mint = read_pubkey(input)?; let account = read_pubkey(&input[32..])?; let amount = read_u64(&input[64..])?; - burn(context, state, mint, account, amount) + burn(context, state, mint, account, amount).await } [0x44, 0xef, 0x32, 0x44] => { // freeze(bytes32 mint, bytes32 account) @@ -189,7 +196,7 @@ pub fn spl_token( let mint = read_pubkey(input)?; let account = read_pubkey(&input[32..])?; - freeze(context, state, mint, account) + freeze(context, state, mint, account).await } [0x3d, 0x71, 0x8c, 0x9a] => { // thaw(bytes32 mint, bytes32 account) @@ -199,7 +206,7 @@ pub fn spl_token( let mint = read_pubkey(input)?; let account = read_pubkey(&input[32..])?; - thaw(context, state, mint, account) + thaw(context, state, mint, account).await } [0xeb, 0x7d, 0xa7, 0x8c] => { // findAccount(bytes32 seed) @@ -209,17 +216,17 @@ pub fn spl_token( [0x6d, 0xa9, 0xde, 0x75] => { // isSystemAccount(bytes32 account) let account = read_pubkey(input)?; - is_system_account(context, state, account) + is_system_account(context, state, account).await } [0xd1, 0xde, 0x50, 0x11] => { // getAccount(bytes32 account) let account = read_pubkey(input)?; - get_account(context, state, account) + get_account(context, state, account).await } [0xa2, 0xce, 0x9c, 0x1f] => { // getMint(bytes32 account) let account = read_pubkey(input)?; - get_mint(context, state, account) + get_mint(context, state, account).await } _ => Err(Error::UnknownPrecompileMethodSelector(*address, selector)), } @@ -255,42 +262,17 @@ fn read_salt(input: &[u8]) -> Result<&[u8; 32]> { Ok(arrayref::array_ref![input, 0, 32]) } -fn create_account( - state: &mut ExecutorState, - account: &OwnedAccountInfo, - space: usize, - seeds: Vec>, -) -> Result<()> { - let rent = Rent::get()?; - let minimum_balance = rent.minimum_balance(space); - - let required_lamports = minimum_balance.saturating_sub(account.lamports); - - if required_lamports > 0 { - let transfer = - system_instruction::transfer(state.backend.operator(), &account.key, required_lamports); - state.queue_external_instruction(transfer, vec![], required_lamports); - } - - let allocate = system_instruction::allocate(&account.key, space.try_into().unwrap()); - state.queue_external_instruction(allocate, seeds.clone(), 0); - - let assign = system_instruction::assign(&account.key, &spl_token::ID); - state.queue_external_instruction(assign, seeds, 0); - - Ok(()) -} - -fn initialize_mint( +#[maybe_async] +async fn initialize_mint( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, seed: &[u8], decimals: u8, mint_authority: Option, freeze_authority: Option, -) -> Result> { +) -> Result> { let signer = context.caller; - let (signer_pubkey, _) = state.backend.solana_address(&signer); + let (signer_pubkey, _) = state.contract_pubkey(signer); let (mint_key, bump_seed) = Pubkey::find_program_address( &[ @@ -299,23 +281,30 @@ fn initialize_mint( signer.as_bytes(), seed, ], - state.backend.program_id(), + state.program_id(), ); - let account = state.external_account(mint_key)?; + let account = state.external_account(mint_key).await?; if !system_program::check_id(&account.owner) { return Err(Error::AccountInvalidOwner(mint_key, system_program::ID)); } - let seeds: Vec> = vec![ - vec![ACCOUNT_SEED_VERSION], - b"ContractData".to_vec(), - signer.as_bytes().to_vec(), - seed.to_vec(), - vec![bump_seed], + let seeds: Vector> = vector![ + vector![ACCOUNT_SEED_VERSION], + b"ContractData".to_vector(), + signer.as_bytes().to_vector(), + seed.to_vector(), + vector![bump_seed], ]; - create_account(state, &account, spl_token::state::Mint::LEN, seeds)?; + create_account( + state, + &account, + spl_token::state::Mint::LEN, + &spl_token::ID, + seeds, + ) + .await?; let initialize_mint = spl_token::instruction::initialize_mint( &spl_token::ID, @@ -324,20 +313,23 @@ fn initialize_mint( Some(&freeze_authority.unwrap_or(signer_pubkey)), decimals, )?; - state.queue_external_instruction(initialize_mint, vec![], 0); + state + .queue_external_instruction(initialize_mint, vector![], 0, true) + .await?; - Ok(mint_key.to_bytes().to_vec()) + Ok(mint_key.to_bytes().to_vector()) } -fn initialize_account( +#[maybe_async] +async fn initialize_account( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, seed: &[u8], mint: Pubkey, owner: Option, -) -> Result> { +) -> Result> { let signer = context.caller; - let (signer_pubkey, _) = state.backend.solana_address(&signer); + let (signer_pubkey, _) = state.contract_pubkey(signer); let (account_key, bump_seed) = Pubkey::find_program_address( &[ @@ -346,23 +338,30 @@ fn initialize_account( signer.as_bytes(), seed, ], - state.backend.program_id(), + state.program_id(), ); - let account = state.external_account(account_key)?; + let account = state.external_account(account_key).await?; if !system_program::check_id(&account.owner) { return Err(Error::AccountInvalidOwner(account_key, system_program::ID)); } - let seeds: Vec> = vec![ - vec![ACCOUNT_SEED_VERSION], - b"ContractData".to_vec(), - signer.as_bytes().to_vec(), - seed.to_vec(), - vec![bump_seed], + let seeds: Vector> = vector![ + vector![ACCOUNT_SEED_VERSION], + b"ContractData".to_vector(), + signer.as_bytes().to_vector(), + seed.to_vector(), + vector![bump_seed], ]; - create_account(state, &account, spl_token::state::Account::LEN, seeds)?; + create_account( + state, + &account, + spl_token::state::Account::LEN, + &spl_token::ID, + seeds, + ) + .await?; let initialize_mint = spl_token::instruction::initialize_account2( &spl_token::ID, @@ -370,51 +369,57 @@ fn initialize_account( &mint, &owner.unwrap_or(signer_pubkey), )?; - state.queue_external_instruction(initialize_mint, vec![], 0); + state + .queue_external_instruction(initialize_mint, vector![], 0, true) + .await?; - Ok(account_key.to_bytes().to_vec()) + Ok(account_key.to_bytes().to_vector()) } -fn close_account( +#[maybe_async] +async fn close_account( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, account: Pubkey, -) -> Result> { +) -> Result> { let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; let close_account = spl_token::instruction::close_account( &spl_token::ID, &account, - state.backend.operator(), + &FAKE_OPERATOR, &signer_pubkey, &[], )?; - state.queue_external_instruction(close_account, seeds, 0); + state + .queue_external_instruction(close_account, vector![seeds], 0, true) + .await?; - Ok(vec![]) + Ok(vector![]) } -fn approve( +#[maybe_async] +async fn approve( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, source: Pubkey, target: Pubkey, amount: u64, -) -> Result> { +) -> Result> { let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; let approve = spl_token::instruction::approve( @@ -425,45 +430,55 @@ fn approve( &[], amount, )?; - state.queue_external_instruction(approve, seeds, 0); + state + .queue_external_instruction(approve, vector![seeds], 0, true) + .await?; - Ok(vec![]) + Ok(vector![]) } -fn revoke( +#[maybe_async] +async fn revoke( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, account: Pubkey, -) -> Result> { +) -> Result> { let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; let revoke = spl_token::instruction::revoke(&spl_token::ID, &account, &signer_pubkey, &[])?; - state.queue_external_instruction(revoke, seeds, 0); + state + .queue_external_instruction(revoke, vector![seeds], 0, true) + .await?; - Ok(vec![]) + Ok(vector![]) } -fn transfer( +#[maybe_async] +async fn transfer( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, source: Pubkey, target: Pubkey, amount: u64, -) -> Result> { +) -> Result> { + if (source == target) || (amount == 0) { + return Ok(vector![]); + } + let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; let transfer = spl_token::instruction::transfer( @@ -474,34 +489,40 @@ fn transfer( &[], amount, )?; - state.queue_external_instruction(transfer, seeds, 0); + state + .queue_external_instruction(transfer, vector![seeds], 0, true) + .await?; - Ok(vec![]) + Ok(vector![]) } -fn transfer_with_seed( +#[maybe_async] +async fn transfer_with_seed( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, seed: &[u8; 32], source: Pubkey, target: Pubkey, amount: u64, -) -> Result> { +) -> Result> { + if (source == target) || (amount == 0) { + return Ok(vector![]); + } + let seeds: &[&[u8]] = &[ &[ACCOUNT_SEED_VERSION], b"AUTH", context.caller.as_bytes(), seed, ]; - let (signer_pubkey, signer_seed) = - Pubkey::find_program_address(seeds, state.backend.program_id()); - - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - b"AUTH".to_vec(), - context.caller.as_bytes().to_vec(), - seed.to_vec(), - vec![signer_seed], + let (signer_pubkey, signer_seed) = Pubkey::find_program_address(seeds, state.program_id()); + + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + b"AUTH".to_vector(), + context.caller.as_bytes().to_vector(), + seed.to_vector(), + vector![signer_seed], ]; let transfer = spl_token::instruction::transfer( @@ -512,25 +533,32 @@ fn transfer_with_seed( &[], amount, )?; - state.queue_external_instruction(transfer, seeds, 0); + state + .queue_external_instruction(transfer, vector![seeds], 0, true) + .await?; - Ok(vec![]) + Ok(vector![]) } -fn mint_to( +#[maybe_async] +async fn mint_to( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, mint: Pubkey, target: Pubkey, amount: u64, -) -> Result> { +) -> Result> { + if amount == 0 { + return Ok(vector![]); + } + let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; let mint_to = spl_token::instruction::mint_to( @@ -541,25 +569,32 @@ fn mint_to( &[], amount, )?; - state.queue_external_instruction(mint_to, seeds, 0); + state + .queue_external_instruction(mint_to, vector![seeds], 0, true) + .await?; - Ok(vec![]) + Ok(vector![]) } -fn burn( +#[maybe_async] +async fn burn( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, mint: Pubkey, source: Pubkey, amount: u64, -) -> Result> { +) -> Result> { + if amount == 0 { + return Ok(vector![]); + } + let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; #[rustfmt::skip] @@ -571,24 +606,27 @@ fn burn( &[], amount )?; - state.queue_external_instruction(burn, seeds, 0); + state + .queue_external_instruction(burn, vector![seeds], 0, true) + .await?; - Ok(vec![]) + Ok(vector![]) } -fn freeze( +#[maybe_async] +async fn freeze( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, mint: Pubkey, target: Pubkey, -) -> Result> { +) -> Result> { let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; let freeze = spl_token::instruction::freeze_account( @@ -598,24 +636,27 @@ fn freeze( &signer_pubkey, &[], )?; - state.queue_external_instruction(freeze, seeds, 0); + state + .queue_external_instruction(freeze, vector![seeds], 0, true) + .await?; - Ok(vec![]) + Ok(vector![]) } -fn thaw( +#[maybe_async] +async fn thaw( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &mut State, mint: Pubkey, target: Pubkey, -) -> Result> { +) -> Result> { let signer = context.caller; - let (signer_pubkey, bump_seed) = state.backend.solana_address(&signer); + let (signer_pubkey, bump_seed) = state.contract_pubkey(signer); - let seeds = vec![ - vec![ACCOUNT_SEED_VERSION], - signer.as_bytes().to_vec(), - vec![bump_seed], + let seeds = vector![ + vector![ACCOUNT_SEED_VERSION], + signer.as_bytes().to_vector(), + vector![bump_seed], ]; #[rustfmt::skip] @@ -626,17 +667,19 @@ fn thaw( &signer_pubkey, &[] )?; - state.queue_external_instruction(thaw, seeds, 0); + state + .queue_external_instruction(thaw, vector![seeds], 0, true) + .await?; - Ok(vec![]) + Ok(vector![]) } #[allow(clippy::unnecessary_wraps)] -fn find_account( +fn find_account( context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, seed: &[u8], -) -> Result> { +) -> Result> { let signer = context.caller; let (account_key, _) = Pubkey::find_program_address( @@ -646,34 +689,36 @@ fn find_account( signer.as_bytes(), seed, ], - state.backend.program_id(), + state.program_id(), ); - Ok(account_key.to_bytes().to_vec()) + Ok(account_key.to_bytes().to_vector()) } -fn is_system_account( +#[maybe_async] +async fn is_system_account( _context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, account: Pubkey, -) -> Result> { - let account = state.external_account(account)?; +) -> Result> { + let account = state.external_account(account).await?; if system_program::check_id(&account.owner) { - let mut result = vec![0_u8; 32]; + let mut result = vector![0_u8; 32]; result[31] = 1; // return true Ok(result) } else { - Ok(vec![0_u8; 32]) + Ok(vector![0_u8; 32]) } } -fn get_account( +#[maybe_async] +async fn get_account( _context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, account: Pubkey, -) -> Result> { - let account = state.external_account(account)?; +) -> Result> { + let account = state.external_account(account).await?; let token = if spl_token::check_id(&account.owner) { spl_token::state::Account::unpack(&account.data)? } else if system_program::check_id(&account.owner) { @@ -699,15 +744,16 @@ fn get_account( .unwrap_or_default(); state[31] = token.state as u8; - Ok(result.to_vec()) + Ok(result.to_vector()) } -fn get_mint( +#[maybe_async] +async fn get_mint( _context: &crate::evm::Context, - state: &mut ExecutorState, + state: &State, account: Pubkey, -) -> Result> { - let account = state.external_account(account)?; +) -> Result> { + let account = state.external_account(account).await?; let mint = if spl_token::check_id(&account.owner) { spl_token::state::Mint::unpack(&account.data)? } else if system_program::check_id(&account.owner) { @@ -734,5 +780,5 @@ fn get_mint( .map(Pubkey::to_bytes) .unwrap_or_default(); - Ok(result.to_vec()) + Ok(result.to_vector()) } diff --git a/evm_loader/program/src/executor/state.rs b/evm_loader/program/src/executor/state.rs index 031d855f3..954525c81 100644 --- a/evm_loader/program/src/executor/state.rs +++ b/evm_loader/program/src/executor/state.rs @@ -1,299 +1,318 @@ use std::cell::RefCell; use std::collections::BTreeMap; -use ethnum::{AsU256, U256}; -use solana_program::instruction::Instruction; -use solana_program::pubkey::Pubkey; - -use crate::account_storage::AccountStorage; +use crate::account_storage::{AccountStorage, LogCollector}; use crate::error::{Error, Result}; use crate::evm::database::Database; use crate::evm::{Context, ExitStatus}; use crate::types::Address; +use ethnum::{AsU256, U256}; +use maybe_async::maybe_async; +use mpl_token_metadata::programs::MPL_TOKEN_METADATA_ID; +use solana_program::instruction::Instruction; +use solana_program::pubkey::Pubkey; +use solana_program::rent::Rent; + +use crate::allocator::acc_allocator; +use crate::types::tree_map::TreeMap; +use crate::types::vector::{Vector, VectorSliceExt, VectorSliceSlowExt}; use super::action::Action; use super::cache::Cache; +use super::precompile_extension::PrecompiledContracts; use super::OwnedAccountInfo; +pub type ExecutionResult<'a> = Option<(&'a ExitStatus, &'a Vector)>; +pub type TouchedAccounts = TreeMap; + /// Represents the state of executor abstracted away from a self.backend. -/// UPDATE `serialize/deserialize` WHEN THIS STRUCTURE CHANGES -pub struct ExecutorState<'a, B: AccountStorage> { - pub backend: &'a B, +/// Persistent part of `ExecutorState`. +#[repr(C)] +pub struct ExecutorStateData { cache: RefCell, - actions: Vec, - stack: Vec, + actions: Vector, + stack: Vector, exit_status: Option, + touched_accounts: RefCell, } -impl<'a, B: AccountStorage> ExecutorState<'a, B> { - pub fn serialize_into(&self, buffer: &mut [u8]) -> Result { - let mut cursor = std::io::Cursor::new(buffer); - - let value = (&self.cache, &self.actions, &self.stack, &self.exit_status); - bincode::serialize_into(&mut cursor, &value)?; - - cursor.position().try_into().map_err(Error::from) - } - - pub fn deserialize_from(buffer: &[u8], backend: &'a B) -> Result { - let (cache, actions, stack, exit_status) = bincode::deserialize(buffer)?; - Ok(Self { - backend, - cache, - actions, - stack, - exit_status, - }) - } +pub struct ExecutorState<'a, B: AccountStorage> { + pub backend: &'a mut B, + pub data: &'a mut ExecutorStateData, +} - #[must_use] - pub fn new(backend: &'a B) -> Self { +impl<'a> ExecutorStateData { + pub fn new(backend: &B) -> Self { let cache = Cache { - solana_accounts: BTreeMap::new(), block_number: backend.block_number(), block_timestamp: backend.block_timestamp(), }; Self { - backend, cache: RefCell::new(cache), - actions: Vec::with_capacity(64), - stack: Vec::with_capacity(16), + actions: Vector::with_capacity_in(64, acc_allocator()), + stack: Vector::with_capacity_in(16, acc_allocator()), exit_status: None, + touched_accounts: RefCell::new(TouchedAccounts::new()), } } - pub fn into_actions(self) -> Vec { - assert!(self.stack.is_empty()); + #[must_use] + pub fn deconstruct(&'a mut self) -> (ExecutionResult<'a>, TouchedAccounts) { + let result = if let Some(exit_status) = self.exit_status.as_ref() { + Some((exit_status, &self.actions)) + } else { + None + }; + // Move out the current touched_accounts and replace with a new empty one. The previous touched_accounts object + // is consumed by the caller to update touched_accounts inside StateAccount's Data. + ( + result, + self.touched_accounts.replace(TouchedAccounts::new()), + ) + } - self.actions + #[must_use] + pub fn into_actions(&'a self) -> &'a Vector { + &self.actions } +} +impl<'a, B: AccountStorage> ExecutorState<'a, B> { + #[must_use] + pub fn new(backend: &'a mut B, data: &'a mut ExecutorStateData) -> Self { + Self { backend, data } + } + + #[must_use] pub fn exit_status(&self) -> Option<&ExitStatus> { - self.exit_status.as_ref() + self.data.exit_status.as_ref() } pub fn set_exit_status(&mut self, status: ExitStatus) { - self.exit_status = Some(status); + assert!(self.data.stack.is_empty()); + + self.data.exit_status = Some(status); } + #[must_use] pub fn call_depth(&self) -> usize { - self.stack.len() + self.data.stack.len() } - pub fn withdraw_neons(&mut self, source: Address, value: U256) { - let withdraw = Action::NeonWithdraw { source, value }; - self.actions.push(withdraw); + #[maybe_async] + async fn balance_internal(&self, from_address: Address, from_chain_id: u64) -> Result { + let mut balance = self.backend.balance(from_address, from_chain_id).await; + + for action in &self.data.actions { + match action { + Action::Transfer { + source, + target, + chain_id, + value, + } if (&from_chain_id == chain_id) => { + if &from_address == source { + balance = balance.checked_sub(*value).ok_or(Error::IntegerOverflow)?; + } + + if &from_address == target { + balance = balance.checked_add(*value).ok_or(Error::IntegerOverflow)?; + } + } + Action::Burn { + source, + chain_id, + value, + } if (&from_chain_id == chain_id) && (&from_address == source) => { + balance = balance.checked_sub(*value).ok_or(Error::IntegerOverflow)?; + } + _ => {} + } + } + + Ok(balance) } - pub fn queue_external_instruction( - &mut self, - instruction: Instruction, - seeds: Vec>, - fee: u64, - ) { - let action = Action::ExternalInstruction { - program_id: instruction.program_id, - data: instruction.data, - accounts: instruction.accounts, - seeds, - fee, - }; + fn touch_balance(&self, address: Address, chain_id: u64) { + let (pubkey, _) = self.backend.balance_pubkey(address, chain_id); + self.touch_account(pubkey, 2); + } - self.actions.push(action); + fn touch_balance_indirect(&self, address: Address, chain_id: u64) { + let (pubkey, _) = self.backend.balance_pubkey(address, chain_id); + self.touch_account(pubkey, 1); } - pub fn external_account(&self, address: Pubkey) -> Result { - let mut cache = self.cache.borrow_mut(); + fn touch_contract(&self, address: Address) { + let (pubkey, _) = self.backend.contract_pubkey(address); + self.touch_account(pubkey, 2); + } - let metas = self - .actions - .iter() - .filter_map(|a| { - if let Action::ExternalInstruction { accounts, .. } = a { - Some(accounts) - } else { - None - } - }) - .flatten() - .collect::>(); + fn touch_storage(&self, address: Address, index: U256) { + let pubkey = self.backend.storage_cell_pubkey(address, index); + self.touch_account(pubkey, 2); + } - if !metas.iter().any(|m| (m.pubkey == address) && m.is_writable) { - return Ok(cache.get_account_or_insert(address, self.backend).clone()); - } + fn touch_solana(&self, pubkey: Pubkey) { + self.touch_account(pubkey, 2); + } - let mut accounts = metas - .into_iter() - .map(|m| { - ( - m.pubkey, - cache.get_account_or_insert(m.pubkey, self.backend).clone(), - ) - }) - .collect::>(); + fn touch_account(&self, pubkey: Pubkey, count: u64) { + let mut touched_accounts = self.data.touched_accounts.borrow_mut(); - for action in &self.actions { - if let Action::ExternalInstruction { - program_id, - data, - accounts: meta, - .. - } = action - { - match program_id { - program_id if solana_program::system_program::check_id(program_id) => { - crate::external_programs::system::emulate(data, meta, &mut accounts)?; - } - program_id if spl_token::check_id(program_id) => { - crate::external_programs::spl_token::emulate(data, meta, &mut accounts)?; - } - program_id if spl_associated_token_account::check_id(program_id) => { - crate::external_programs::spl_associated_token::emulate( - data, - meta, - &mut accounts, - )?; - } - program_id if mpl_token_metadata::check_id(program_id) => { - crate::external_programs::metaplex::emulate(data, meta, &mut accounts)?; - } - _ => { - return Err(Error::Custom(format!( - "Unknown external program: {program_id}" - ))); - } - } - } - } + let cur_counter = touched_accounts + .get(&pubkey) + .map_or(0_u64, |counter| *counter); + // Technically, this could overflow with infinite compute budget + touched_accounts.insert(pubkey, cur_counter.checked_add(count).unwrap()); + } +} - Ok(accounts[&address].clone()) +impl LogCollector for ExecutorState<'_, B> { + fn collect_log( + &mut self, + address: &[u8; 20], + topics: [[u8; 32]; N], + data: &[u8], + ) { + self.backend.collect_log(address, topics, data); } } +#[maybe_async(?Send)] impl<'a, B: AccountStorage> Database for ExecutorState<'a, B> { - fn chain_id(&self) -> U256 { - let chain_id = self.backend.chain_id(); - U256::from(chain_id) + fn program_id(&self) -> &Pubkey { + self.backend.program_id() + } + fn operator(&self) -> Pubkey { + self.backend.operator() + } + fn chain_id_to_token(&self, chain_id: u64) -> Pubkey { + self.backend.chain_id_to_token(chain_id) + } + fn contract_pubkey(&self, address: Address) -> (Pubkey, u8) { + self.backend.contract_pubkey(address) } - fn nonce(&self, from_address: &Address) -> Result { - let mut nonce = self.backend.nonce(from_address); + async fn nonce(&self, from_address: Address, from_chain_id: u64) -> Result { + let mut nonce = self.backend.nonce(from_address, from_chain_id).await; + let mut increment = 0_u64; - for action in &self.actions { - if let Action::EvmIncrementNonce { address } = action { - if from_address == address { - nonce = nonce.checked_add(1).ok_or(Error::IntegerOverflow)?; + for action in &self.data.actions { + if let Action::EvmIncrementNonce { address, chain_id } = action { + if (&from_address == address) && (&from_chain_id == chain_id) { + increment += 1; } } } + nonce = nonce.checked_add(increment).ok_or(Error::IntegerOverflow)?; + Ok(nonce) } - fn increment_nonce(&mut self, address: Address) -> Result<()> { - let increment = Action::EvmIncrementNonce { address }; - self.actions.push(increment); + async fn increment_nonce(&mut self, address: Address, chain_id: u64) -> Result<()> { + let increment = Action::EvmIncrementNonce { address, chain_id }; + self.data.actions.push(increment); Ok(()) } - fn balance(&self, from_address: &Address) -> Result { - let mut balance = self.backend.balance(from_address); - - for action in &self.actions { - match action { - Action::NeonTransfer { - source, - target, - value, - } => { - if from_address == source { - balance = balance.checked_sub(*value).ok_or(Error::IntegerOverflow)?; - } - - if from_address == target { - balance = balance.checked_add(*value).ok_or(Error::IntegerOverflow)?; - } - } - Action::NeonWithdraw { source, value } => { - if from_address == source { - balance = balance.checked_sub(*value).ok_or(Error::IntegerOverflow)?; - } - } - _ => {} - } - } + async fn balance(&self, address: Address, chain_id: u64) -> Result { + self.touch_balance(address, chain_id); - Ok(balance) + self.balance_internal(address, chain_id).await } - fn transfer(&mut self, source: Address, target: Address, value: U256) -> Result<()> { + async fn transfer( + &mut self, + source: Address, + target: Address, + chain_id: u64, + value: U256, + ) -> Result<()> { if value == U256::ZERO { return Ok(()); } + self.touch_contract(target); + + let target_chain_id = self.contract_chain_id(target).await.unwrap_or(chain_id); + + if (self.code_size(target).await? > 0) && (target_chain_id != chain_id) { + return Err(Error::InvalidTransferToken(source, chain_id)); + } + if source == target { return Ok(()); } - if self.balance(&source)? < value { - return Err(Error::InsufficientBalance(source, value)); + self.touch_balance_indirect(source, chain_id); + if self.balance_internal(source, chain_id).await? < value { + return Err(Error::InsufficientBalance(source, chain_id, value)); } - let transfer = Action::NeonTransfer { + let transfer = Action::Transfer { source, target, + chain_id, value, }; - self.actions.push(transfer); + self.data.actions.push(transfer); Ok(()) } - fn code_size(&self, from_address: &Address) -> Result { - if self.is_precompile_extension(from_address) { - return Ok(1); // This is required in order to make a normal call to an extension contract + async fn burn(&mut self, source: Address, chain_id: u64, value: U256) -> Result<()> { + self.touch_balance_indirect(source, chain_id); + if self.balance_internal(source, chain_id).await? < value { + return Err(Error::InsufficientBalance(source, chain_id, value)); } - for action in &self.actions { - if let Action::EvmSetCode { address, code } = action { - if from_address == address { - return Ok(code.len()); - } - } - } + let burn = Action::Burn { + source, + chain_id, + value, + }; + self.data.actions.push(burn); - Ok(self.backend.code_size(from_address)) + Ok(()) } - fn code_hash(&self, from_address: &Address) -> Result<[u8; 32]> { - use solana_program::keccak::hash; + async fn code_size(&self, from_address: Address) -> Result { + if PrecompiledContracts::is_precompile_extension(&from_address) { + return Ok(1); // This is required in order to make a normal call to an extension contract + } + + self.touch_contract(from_address); - for action in &self.actions { - if let Action::EvmSetCode { address, code } = action { - if from_address == address { - return Ok(hash(code).to_bytes()); + for action in &self.data.actions { + if let Action::EvmSetCode { address, code, .. } = action { + if &from_address == address { + return Ok(code.len()); } } } - Ok(self.backend.code_hash(from_address)) + Ok(self.backend.code_size(from_address).await) } - fn code(&self, from_address: &Address) -> Result { - for action in &self.actions { - if let Action::EvmSetCode { address, code } = action { - if from_address == address { - return Ok(code.clone()); + async fn code(&self, from_address: Address) -> Result { + self.touch_contract(from_address); + + for action in &self.data.actions { + if let Action::EvmSetCode { address, code, .. } = action { + if &from_address == address { + return Ok(crate::evm::Buffer::from_slice(code)); } } } - Ok(self.backend.code(from_address)) + Ok(self.backend.code(from_address).await) } - fn set_code(&mut self, address: Address, code: crate::evm::Buffer) -> Result<()> { + async fn set_code(&mut self, address: Address, chain_id: u64, code: Vector) -> Result<()> { if code.starts_with(&[0xEF]) { // https://eips.ethereum.org/EIPS/eip-3541 return Err(Error::EVMObjectFormatNotSupported(address)); @@ -304,48 +323,80 @@ impl<'a, B: AccountStorage> Database for ExecutorState<'a, B> { return Err(Error::ContractCodeSizeLimit(address, code.len())); } - let set_code = Action::EvmSetCode { address, code }; - self.actions.push(set_code); + let set_code = Action::EvmSetCode { + address, + chain_id, + code, + }; + self.data.actions.push(set_code); Ok(()) } - fn selfdestruct(&mut self, address: Address) -> Result<()> { - let suicide = Action::EvmSelfDestruct { address }; - self.actions.push(suicide); + async fn storage(&self, from_address: Address, from_index: U256) -> Result<[u8; 32]> { + self.touch_storage(from_address, from_index); + + for action in self.data.actions.iter().rev() { + if let Action::EvmSetStorage { + address, + index, + value, + } = action + { + if (&from_address == address) && (&from_index == index) { + return Ok(*value); + } + } + } + + Ok(self.backend.storage(from_address, from_index).await) + } + + async fn set_storage(&mut self, address: Address, index: U256, value: [u8; 32]) -> Result<()> { + let set_storage = Action::EvmSetStorage { + address, + index, + value, + }; + self.data.actions.push(set_storage); Ok(()) } - fn storage(&self, from_address: &Address, from_index: &U256) -> Result<[u8; 32]> { - for action in self.actions.iter().rev() { - if let Action::EvmSetStorage { + async fn transient_storage(&self, from_address: Address, from_index: U256) -> Result<[u8; 32]> { + for action in self.data.actions.iter().rev() { + if let Action::EvmSetTransientStorage { address, index, value, } = action { - if (from_address == address) && (from_index == index) { + if (&from_address == address) && (&from_index == index) { return Ok(*value); } } } - Ok(self.backend.storage(from_address, from_index)) + Ok(<[u8; 32]>::default()) } - fn set_storage(&mut self, address: Address, index: U256, value: [u8; 32]) -> Result<()> { - let set_storage = Action::EvmSetStorage { + fn set_transient_storage( + &mut self, + address: Address, + index: U256, + value: [u8; 32], + ) -> Result<()> { + let set_storage = Action::EvmSetTransientStorage { address, index, value, }; - self.actions.push(set_storage); + self.data.actions.push(set_storage); Ok(()) } - fn block_hash(&self, number: U256) -> Result<[u8; 32]> { + async fn block_hash(&self, number: U256) -> Result<[u8; 32]> { // geth: // - checks the overflow // - converts to u64 @@ -356,7 +407,7 @@ impl<'a, B: AccountStorage> Database for ExecutorState<'a, B> { } let number = number.as_u64(); - let block_slot = self.cache.borrow().block_number.as_u64(); + let block_slot = self.data.cache.borrow().block_number.as_u64(); let lower_block_slot = if block_slot < 257 { 0 } else { @@ -367,58 +418,203 @@ impl<'a, B: AccountStorage> Database for ExecutorState<'a, B> { return Ok(<[u8; 32]>::default()); } - Ok(self.backend.block_hash(number)) + Ok(self.backend.block_hash(number).await) } fn block_number(&self) -> Result { - let cache = self.cache.borrow(); + let cache = self.data.cache.borrow(); Ok(cache.block_number) } fn block_timestamp(&self) -> Result { - let cache = self.cache.borrow(); + let cache = self.data.cache.borrow(); Ok(cache.block_timestamp) } - fn map_solana_account(&self, address: &Pubkey, action: F) -> R + async fn external_account(&self, address: Pubkey) -> Result { + self.touch_solana(address); + + let metas = self + .data + .actions + .iter() + .filter_map(|a| { + if let Action::ExternalInstruction { accounts, .. } = a { + Some(accounts) + } else { + None + } + }) + .flatten() + .collect::>(); + + if !metas.iter().any(|m| (m.pubkey == address) && m.is_writable) { + let account = self.backend.clone_solana_account(&address).await; + return Ok(account); + } + + let mut accounts = BTreeMap::::new(); + + for m in metas { + self.touch_solana(m.pubkey); + + let account = self.backend.clone_solana_account(&m.pubkey).await; + accounts.insert(m.pubkey, account); + } + + for action in &self.data.actions { + if let Action::ExternalInstruction { + program_id, + data, + accounts: meta, + emulated_internally, + .. + } = action + { + if !emulated_internally { + unreachable!(); + } + + match program_id { + program_id if solana_program::system_program::check_id(program_id) => { + crate::external_programs::system::emulate(data, meta, &mut accounts)?; + } + program_id if spl_token::check_id(program_id) => { + crate::external_programs::spl_token::emulate(data, meta, &mut accounts)?; + } + program_id if spl_associated_token_account::check_id(program_id) => { + crate::external_programs::spl_associated_token::emulate( + data, + meta, + &mut accounts, + self.rent(), + )?; + } + program_id if &MPL_TOKEN_METADATA_ID == program_id => { + crate::external_programs::metaplex::emulate( + data, + meta, + &mut accounts, + self.rent(), + )?; + } + _ => { + return Err(Error::Custom(format!( + "Unknown external program for emulate: {program_id}" + ))); + } + } + } + } + + Ok(accounts[&address].clone()) + } + + fn rent(&self) -> &Rent { + self.backend.rent() + } + + fn return_data(&self) -> Option<(Pubkey, Vec)> { + self.backend.return_data() + } + + fn set_return_data(&mut self, data: &[u8]) { + self.backend.set_return_data(data); + } + + async fn map_solana_account(&self, address: &Pubkey, action: F) -> R where F: FnOnce(&solana_program::account_info::AccountInfo) -> R, { - self.backend.map_solana_account(address, action) + self.touch_solana(*address); + + self.backend.map_solana_account(address, action).await } fn snapshot(&mut self) { - self.stack.push(self.actions.len()); + self.data.stack.push(self.data.actions.len()); } fn revert_snapshot(&mut self) { let actions_len = self + .data .stack .pop() .expect("Fatal Error: Inconsistent EVM Call Stack"); - self.actions.truncate(actions_len); + self.data.actions.truncate(actions_len); - if self.stack.is_empty() { + if self.data.stack.is_empty() { // sanity check - assert_eq!(self.actions.len(), 1); - assert!(matches!(self.actions[0], Action::EvmIncrementNonce { .. })); + assert!(self.data.actions.is_empty()); } } fn commit_snapshot(&mut self) { - self.stack + self.data + .stack .pop() .expect("Fatal Error: Inconsistent EVM Call Stack"); } - fn precompile_extension( + async fn precompile_extension( &mut self, context: &Context, address: &Address, data: &[u8], is_static: bool, - ) -> Option>> { - self.call_precompile_extension(context, address, data, is_static) + ) -> Option>> { + PrecompiledContracts::call_precompile_extension(self, context, address, data, is_static) + .await + } + + fn default_chain_id(&self) -> u64 { + self.backend.default_chain_id() + } + + fn is_valid_chain_id(&self, chain_id: u64) -> bool { + self.backend.is_valid_chain_id(chain_id) + } + + async fn contract_chain_id(&self, contract: Address) -> Result { + self.touch_contract(contract); + + for action in self.data.actions.iter().rev() { + if let Action::EvmSetCode { + address, chain_id, .. + } = action + { + if &contract == address { + return Ok(*chain_id); + } + } + } + + self.backend.contract_chain_id(contract).await + } + + async fn queue_external_instruction( + &mut self, + instruction: Instruction, + seeds: Vector>>, + fee: u64, + emulated_internally: bool, + ) -> Result<()> { + #[cfg(target_os = "solana")] + if !emulated_internally { + return Err(Error::UnavalableExternalSolanaCall); + } + + let action = Action::ExternalInstruction { + program_id: instruction.program_id, + data: instruction.data.to_vector(), + accounts: instruction.accounts.elementwise_copy_to_vector(), + seeds, + fee, + emulated_internally, + }; + + self.data.actions.push(action); + Ok(()) } } diff --git a/evm_loader/program/src/executor/synced_state.rs b/evm_loader/program/src/executor/synced_state.rs new file mode 100644 index 000000000..a278f19ef --- /dev/null +++ b/evm_loader/program/src/executor/synced_state.rs @@ -0,0 +1,312 @@ +use ethnum::{AsU256, U256}; +use maybe_async::maybe_async; +use solana_program::instruction::Instruction; +use solana_program::pubkey::Pubkey; +use solana_program::rent::Rent; + +use crate::account_storage::{AccountStorage, LogCollector, SyncedAccountStorage}; +use crate::allocator::acc_allocator; +use crate::error::{Error, Result}; +use crate::evm::database::Database; +use crate::evm::Context; +use crate::types::{Address, Vector}; + +use super::precompile_extension::PrecompiledContracts; +use super::OwnedAccountInfo; + +enum Action { + SetTransientStorage { + address: Address, + index: U256, + value: [u8; 32], + }, +} + +pub struct SyncedExecutorState<'a, B: AccountStorage> { + pub backend: &'a mut B, + actions: Vector, + stack: Vector, +} + +impl<'a, B: SyncedAccountStorage> SyncedExecutorState<'a, B> { + #[must_use] + pub fn new(backend: &'a mut B) -> Self { + Self { + backend, + actions: Vector::with_capacity_in(64, acc_allocator()), + stack: Vector::with_capacity_in(16, acc_allocator()), + } + } + + #[must_use] + pub fn backend(&self) -> &B { + self.backend + } +} + +impl LogCollector for SyncedExecutorState<'_, B> { + fn collect_log( + &mut self, + address: &[u8; 20], + topics: [[u8; 32]; N], + data: &[u8], + ) { + self.backend.collect_log(address, topics, data); + } +} + +#[maybe_async(?Send)] +impl<'a, B: SyncedAccountStorage> Database for SyncedExecutorState<'a, B> { + fn program_id(&self) -> &Pubkey { + self.backend.program_id() + } + fn operator(&self) -> Pubkey { + self.backend.operator() + } + fn chain_id_to_token(&self, chain_id: u64) -> Pubkey { + self.backend.chain_id_to_token(chain_id) + } + fn contract_pubkey(&self, address: Address) -> (Pubkey, u8) { + self.backend.contract_pubkey(address) + } + + async fn nonce(&self, from_address: Address, from_chain_id: u64) -> Result { + let nonce = self.backend.nonce(from_address, from_chain_id).await; + Ok(nonce) + } + + async fn increment_nonce(&mut self, address: Address, chain_id: u64) -> Result<()> { + self.backend.increment_nonce(address, chain_id).await?; + Ok(()) + } + + async fn balance(&self, from_address: Address, from_chain_id: u64) -> Result { + let balance = self.backend.balance(from_address, from_chain_id).await; + Ok(balance) + } + + async fn transfer( + &mut self, + source: Address, + target: Address, + chain_id: u64, + value: U256, + ) -> Result<()> { + if value == U256::ZERO { + return Ok(()); + } + + let target_chain_id = self.contract_chain_id(target).await.unwrap_or(chain_id); + + if (self.code_size(target).await? > 0) && (target_chain_id != chain_id) { + return Err(Error::InvalidTransferToken(source, chain_id)); + } + + if source == target { + return Ok(()); + } + + if self.balance(source, chain_id).await? < value { + return Err(Error::InsufficientBalance(source, chain_id, value)); + } + + self.backend + .transfer(source, target, chain_id, value) + .await?; + Ok(()) + } + + async fn burn(&mut self, source: Address, chain_id: u64, value: U256) -> Result<()> { + self.backend.burn(source, chain_id, value).await?; + Ok(()) + } + + async fn code_size(&self, from_address: Address) -> Result { + if PrecompiledContracts::is_precompile_extension(&from_address) { + return Ok(1); // This is required in order to make a normal call to an extension contract + } + + Ok(self.backend.code_size(from_address).await) + } + + async fn code(&self, from_address: Address) -> Result { + Ok(self.backend.code(from_address).await) + } + + async fn set_code(&mut self, address: Address, chain_id: u64, code: Vector) -> Result<()> { + if code.starts_with(&[0xEF]) { + // https://eips.ethereum.org/EIPS/eip-3541 + return Err(Error::EVMObjectFormatNotSupported(address)); + } + + if code.len() > 0x6000 { + // https://eips.ethereum.org/EIPS/eip-170 + return Err(Error::ContractCodeSizeLimit(address, code.len())); + } + + self.backend.set_code(address, chain_id, code).await?; + Ok(()) + } + + async fn storage(&self, from_address: Address, from_index: U256) -> Result<[u8; 32]> { + Ok(self.backend.storage(from_address, from_index).await) + } + + async fn set_storage(&mut self, address: Address, index: U256, value: [u8; 32]) -> Result<()> { + self.backend.set_storage(address, index, value).await?; + Ok(()) + } + + async fn transient_storage(&self, from_address: Address, from_index: U256) -> Result<[u8; 32]> { + for action in self.actions.iter().rev() { + #[allow(irrefutable_let_patterns)] + if let Action::SetTransientStorage { + address, + index, + value, + } = action + { + if (&from_address == address) && (&from_index == index) { + return Ok(*value); + } + } + } + + Ok([0; 32]) + } + + fn set_transient_storage( + &mut self, + address: Address, + index: U256, + value: [u8; 32], + ) -> Result<()> { + self.actions.push(Action::SetTransientStorage { + address, + index, + value, + }); + Ok(()) + } + + async fn block_hash(&self, number: U256) -> Result<[u8; 32]> { + // geth: + // - checks the overflow + // - converts to u64 + // - checks on last 256 blocks + + if number >= u64::MAX.as_u256() { + return Ok(<[u8; 32]>::default()); + } + + let number = number.as_u64(); + let block_slot = self.backend.block_number().as_u64(); + let lower_block_slot = if block_slot < 257 { + 0 + } else { + block_slot - 256 + }; + + if number >= block_slot || lower_block_slot > number { + return Ok(<[u8; 32]>::default()); + } + + Ok(self.backend.block_hash(number).await) + } + + fn block_number(&self) -> Result { + Ok(self.backend.block_number()) + } + + fn block_timestamp(&self) -> Result { + Ok(self.backend.block_timestamp()) + } + + async fn external_account(&self, address: Pubkey) -> Result { + let account = self.backend.clone_solana_account(&address).await; + return Ok(account); + } + + fn rent(&self) -> &Rent { + self.backend.rent() + } + + fn return_data(&self) -> Option<(Pubkey, Vec)> { + self.backend.return_data() + } + + fn set_return_data(&mut self, data: &[u8]) { + self.backend.set_return_data(data); + } + + async fn map_solana_account(&self, address: &Pubkey, action: F) -> R + where + F: FnOnce(&solana_program::account_info::AccountInfo) -> R, + { + self.backend.map_solana_account(address, action).await + } + + fn snapshot(&mut self) { + self.stack.push(self.actions.len()); + self.backend.snapshot(); + } + + fn revert_snapshot(&mut self) { + let actions_len = self + .stack + .pop() + .expect("Fatal Error: Inconsistent EVM Call Stack"); + + self.actions.truncate(actions_len); + + if self.stack.is_empty() { + // sanity check + assert!(self.actions.is_empty()); + } + + self.backend.revert_snapshot(); + } + + fn commit_snapshot(&mut self) { + self.stack + .pop() + .expect("Fatal Error: Inconsistent EVM Call Stack"); + self.backend.commit_snapshot(); + } + + async fn precompile_extension( + &mut self, + context: &Context, + address: &Address, + data: &[u8], + is_static: bool, + ) -> Option>> { + PrecompiledContracts::call_precompile_extension(self, context, address, data, is_static) + .await + } + + fn default_chain_id(&self) -> u64 { + self.backend.default_chain_id() + } + + fn is_valid_chain_id(&self, chain_id: u64) -> bool { + self.backend.is_valid_chain_id(chain_id) + } + + async fn contract_chain_id(&self, contract: Address) -> Result { + self.backend.contract_chain_id(contract).await + } + + async fn queue_external_instruction( + &mut self, + instruction: Instruction, + seeds: Vector>>, + fee: u64, + emulated_internally: bool, + ) -> Result<()> { + self.backend + .execute_external_instruction(instruction, seeds, fee, emulated_internally) + .await?; + Ok(()) + } +} diff --git a/evm_loader/program/src/external_programs/metaplex.rs b/evm_loader/program/src/external_programs/metaplex.rs index 8176d81a5..81fa938ae 100644 --- a/evm_loader/program/src/external_programs/metaplex.rs +++ b/evm_loader/program/src/external_programs/metaplex.rs @@ -1,48 +1,49 @@ +use crate::error::Result; use borsh::{BorshDeserialize, BorshSerialize}; -use mpl_token_metadata::assertions::collection::assert_collection_update_is_valid; -use mpl_token_metadata::assertions::uses::assert_valid_use; -use mpl_token_metadata::utils::{assert_data_valid, assert_initialized, puff_out_data_fields}; -use solana_program::account_info::IntoAccountInfo; +use mpl_token_metadata::{ + accounts::{MasterEdition, Metadata}, + instructions::{CreateMasterEditionV3InstructionArgs, CreateMetadataAccountV3InstructionArgs}, + programs::MPL_TOKEN_METADATA_ID, + types::{Key, TokenStandard}, +}; use solana_program::instruction::AccountMeta; use solana_program::program_option::COption; use solana_program::rent::Rent; -use solana_program::sysvar::Sysvar; -use spl_token::state::Mint; +use solana_program::{account_info::IntoAccountInfo, program_pack::Pack}; use std::collections::BTreeMap; use crate::executor::OwnedAccountInfo; -use mpl_token_metadata::instruction::{ - CreateMasterEditionArgs, CreateMetadataAccountArgsV3, MetadataInstruction, -}; -use mpl_token_metadata::state::{ - CollectionDetails, Key, MasterEditionV2, Metadata, TokenMetadataAccount, TokenStandard, - MAX_MASTER_EDITION_LEN, MAX_METADATA_LEN, -}; -use solana_program::{ - entrypoint::ProgramResult, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, -}; +use crate::types::vector::VectorVecExt; +use solana_program::pubkey::Pubkey; pub fn emulate( instruction: &[u8], meta: &[AccountMeta], accounts: &mut BTreeMap, -) -> ProgramResult { - match MetadataInstruction::try_from_slice(instruction)? { - MetadataInstruction::CreateMetadataAccountV3(args) => { - create_metadata_accounts_v3(meta, accounts, &args) + rent: &Rent, +) -> Result<()> { + let discriminator = instruction[0]; + let data = &instruction[1..]; + + match discriminator { + 33 => { + let args = CreateMetadataAccountV3InstructionArgs::try_from_slice(data)?; + create_metadata_accounts_v3(meta, accounts, args, rent) } - MetadataInstruction::CreateMasterEditionV3(args) => { - create_master_edition_v3(meta, accounts, &args) + 17 => { + let args = CreateMasterEditionV3InstructionArgs::try_from_slice(data)?; + create_master_edition_v3(meta, accounts, args.max_supply, rent) } - _ => Err!(ProgramError::InvalidInstructionData; "Unknown Metaplex instruction"), + _ => Err("Unknown Metaplex instruction".into()), } } fn create_metadata_accounts_v3( meta: &[AccountMeta], accounts: &mut BTreeMap, - args: &CreateMetadataAccountArgsV3, -) -> ProgramResult { + args: CreateMetadataAccountV3InstructionArgs, + rent: &Rent, +) -> Result<()> { let metadata_account_key = &meta[0].pubkey; let mint_key = &meta[1].pubkey; // let _mint_authority_key = &meta[2].pubkey; @@ -51,79 +52,40 @@ fn create_metadata_accounts_v3( // let _system_account_key = &meta[5].pubkey; // let _rent_key = &meta[6].pubkey; - let mut metadata: Metadata = { - let rent = Rent::get()?; - - let metadata_account = accounts.get_mut(metadata_account_key).unwrap(); - metadata_account.data.resize(MAX_METADATA_LEN, 0); - metadata_account.owner = mpl_token_metadata::ID; - metadata_account.lamports = metadata_account - .lamports - .max(rent.minimum_balance(MAX_METADATA_LEN)); - - let metadata_account_info = metadata_account.into_account_info(); - Metadata::from_account_info(&metadata_account_info)? - }; - - let mint: Mint = { + let mint = { let mint_info = accounts.get_mut(mint_key).unwrap().into_account_info(); - assert_initialized(&mint_info)? + crate::account::token::Mint::from_account(&mint_info)?.into_data() }; - let compatible_data = args.data.to_v1(); - assert_data_valid( - &compatible_data, - update_authority_key, - &metadata, - false, - meta[4].is_signer, - )?; - - metadata.mint = *mint_key; - metadata.key = Key::MetadataV1; - metadata.data = compatible_data; - metadata.is_mutable = args.is_mutable; - metadata.update_authority = *update_authority_key; - - assert_valid_use(&args.data.uses, &None)?; - metadata.uses = args.data.uses.clone(); - - assert_collection_update_is_valid(false, &None, &args.data.collection)?; - metadata.collection = args.data.collection.clone(); - - if let Some(details) = &args.collection_details { - match details { - CollectionDetails::V1 { size: _size } => { - metadata.collection_details = Some(CollectionDetails::V1 { size: 0 }); - } - } - } else { - metadata.collection_details = None; - } - - let token_standard = if mint.decimals == 0 { - TokenStandard::FungibleAsset - } else { - TokenStandard::Fungible + let (_, edition_bump_seed) = MasterEdition::find_pda(mint_key); + + let metadata = Metadata { + key: Key::MetadataV1, + update_authority: *update_authority_key, + mint: *mint_key, + name: args.data.name, + symbol: args.data.symbol, + uri: args.data.uri, + seller_fee_basis_points: args.data.seller_fee_basis_points, + creators: args.data.creators, + primary_sale_happened: false, + is_mutable: args.is_mutable, + edition_nonce: Some(edition_bump_seed), + token_standard: if mint.decimals == 0 { + Some(TokenStandard::FungibleAsset) + } else { + Some(TokenStandard::Fungible) + }, + collection: args.data.collection, + uses: args.data.uses, + collection_details: args.collection_details, + programmable_config: None, }; - metadata.token_standard = Some(token_standard); - puff_out_data_fields(&mut metadata); - - let edition_seeds = &[ - mpl_token_metadata::state::PREFIX.as_bytes(), - mpl_token_metadata::ID.as_ref(), - metadata.mint.as_ref(), - mpl_token_metadata::state::EDITION.as_bytes(), - ]; - let (_, edition_bump_seed) = - Pubkey::find_program_address(edition_seeds, &mpl_token_metadata::ID); - metadata.edition_nonce = Some(edition_bump_seed); - - { - let metadata_account = accounts.get_mut(metadata_account_key).unwrap(); - metadata.serialize(&mut metadata_account.data.as_mut_slice())?; - } + let metadata_account = accounts.get_mut(metadata_account_key).unwrap(); + metadata_account.owner = MPL_TOKEN_METADATA_ID; + metadata_account.data = metadata.try_to_vec()?.into_vector(); + metadata_account.lamports = rent.minimum_balance(metadata_account.data.len()); Ok(()) } @@ -131,8 +93,9 @@ fn create_metadata_accounts_v3( fn create_master_edition_v3( meta: &[AccountMeta], accounts: &mut BTreeMap, - args: &CreateMasterEditionArgs, -) -> ProgramResult { + max_supply: Option, + rent: &Rent, +) -> Result<()> { let edition_account_key = &meta[0].pubkey; let mint_key = &meta[1].pubkey; // let update_authority_key = &meta[2].pubkey; @@ -144,55 +107,48 @@ fn create_master_edition_v3( // let _rent_key = &meta[8].pubkey; let mut metadata: Metadata = { - let metadata_info = accounts - .get_mut(metadata_account_key) - .unwrap() - .into_account_info(); - Metadata::from_account_info(&metadata_info)? + let metadata_account = accounts.get_mut(metadata_account_key).unwrap(); + Metadata::from_bytes(&metadata_account.data)? }; - let mut mint: Mint = { + let mut mint = { let mint_info = accounts.get_mut(mint_key).unwrap().into_account_info(); - assert_initialized(&mint_info)? + crate::account::token::Mint::from_account(&mint_info)?.into_data() }; if &metadata.mint != mint_key { - return Err!(ProgramError::InvalidArgument; "Metaplex: Invalid token mint"); + return Err("Metaplex: Invalid token mint".into()); } if mint.decimals != 0 { - return Err!(ProgramError::InvalidArgument; "Metaplex: mint decimals != 0"); + return Err("Metaplex: mint decimals != 0".into()); } if mint.supply != 1 { - return Err!(ProgramError::InvalidArgument; "Metaplex: mint supply != 1"); + return Err("Metaplex: mint supply != 1".into()); } - { - let rent = Rent::get()?; + let edition = MasterEdition { + key: Key::MasterEditionV2, + supply: 0, + max_supply, + }; + // Master Edition Account + { let edition_account = accounts.get_mut(edition_account_key).unwrap(); - edition_account.data.resize(MAX_MASTER_EDITION_LEN, 0); - edition_account.owner = mpl_token_metadata::ID; - edition_account.lamports = edition_account - .lamports - .max(rent.minimum_balance(MAX_MASTER_EDITION_LEN)); - - let edition = MasterEditionV2 { - key: Key::MasterEditionV2, - supply: 0, - max_supply: args.max_supply, - }; - edition.serialize(&mut edition_account.data.as_mut_slice())?; + edition_account.owner = MPL_TOKEN_METADATA_ID; + edition_account.data = edition.try_to_vec()?.into_vector(); + edition_account.lamports = rent.minimum_balance(edition_account.data.len()); } - + // Metadata Account { let metadata_account = accounts.get_mut(metadata_account_key).unwrap(); metadata.token_standard = Some(TokenStandard::NonFungible); metadata.serialize(&mut metadata_account.data.as_mut_slice())?; } - + // Mint Account { mint.mint_authority = COption::Some(*edition_account_key); if mint.freeze_authority.is_some() { diff --git a/evm_loader/program/src/external_programs/spl_associated_token.rs b/evm_loader/program/src/external_programs/spl_associated_token.rs index 9ef2ae843..0491cc903 100644 --- a/evm_loader/program/src/external_programs/spl_associated_token.rs +++ b/evm_loader/program/src/external_programs/spl_associated_token.rs @@ -4,7 +4,7 @@ use crate::executor::OwnedAccountInfo; use borsh::BorshDeserialize; use solana_program::{ entrypoint::ProgramResult, instruction::AccountMeta, program_error::ProgramError, - program_pack::Pack, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, + program_pack::Pack, pubkey::Pubkey, rent::Rent, }; use spl_associated_token_account::instruction::AssociatedTokenAccountInstruction; @@ -12,6 +12,7 @@ pub fn emulate( instruction: &[u8], meta: &[AccountMeta], accounts: &mut BTreeMap, + rent: &Rent, ) -> ProgramResult { let instruction = if instruction.is_empty() { AssociatedTokenAccountInstruction::Create @@ -33,8 +34,6 @@ pub fn emulate( let required_lamports = { let associated_token_account = &accounts[associated_token_account_key]; - - let rent = Rent::get()?; rent.minimum_balance(spl_token::state::Account::LEN) .max(1) .saturating_sub(associated_token_account.lamports) diff --git a/evm_loader/program/src/gasometer.rs b/evm_loader/program/src/gasometer.rs index 797fba36b..025a85b5a 100644 --- a/evm_loader/program/src/gasometer.rs +++ b/evm_loader/program/src/gasometer.rs @@ -9,32 +9,38 @@ use solana_program::program_error::ProgramError; pub const LAMPORTS_PER_SIGNATURE: u64 = 5000; const WRITE_TO_HOLDER_TRX_COST: u64 = LAMPORTS_PER_SIGNATURE; -const CANCEL_TRX_COST: u64 = LAMPORTS_PER_SIGNATURE; -const LAST_ITERATION_COST: u64 = LAMPORTS_PER_SIGNATURE; +pub const CANCEL_TRX_COST: u64 = LAMPORTS_PER_SIGNATURE; +pub const LAST_ITERATION_COST: u64 = LAMPORTS_PER_SIGNATURE; pub struct Gasometer { paid_gas: U256, gas: u64, + refund: u64, operator_balance: u64, } impl Gasometer { - pub fn new(paid_gas: Option, operator: &Operator) -> Result { + pub fn new(paid_gas: U256, operator: &Operator) -> Result { Ok(Self { - paid_gas: paid_gas.unwrap_or(U256::ZERO), + paid_gas, gas: 0_u64, + refund: 0_u64, operator_balance: operator.lamports(), }) } #[must_use] pub fn used_gas(&self) -> U256 { - U256::from(self.gas) + U256::from(self.gas.saturating_sub(self.refund)) } #[must_use] pub fn used_gas_total(&self) -> U256 { - self.paid_gas.saturating_add(U256::from(self.gas)) + self.paid_gas.saturating_add(self.used_gas()) + } + + pub fn refund_lamports(&mut self, lamports: u64) { + self.refund = self.refund.saturating_add(lamports); } pub fn record_operator_expenses(&mut self, operator: &Operator) { @@ -47,17 +53,8 @@ impl Gasometer { self.gas = self.gas.saturating_add(LAMPORTS_PER_SIGNATURE); } - pub fn record_iterative_overhead(&mut self) { - // High chance of last iteration to fail with solana error - // Consume gas for it in the first iteration - self.gas = self - .gas - .saturating_add(LAST_ITERATION_COST) - .saturating_add(CANCEL_TRX_COST); - } - pub fn record_write_to_holder(&mut self, trx: &Transaction) { - let size: u64 = trx.rlp_len.try_into().expect("usize is 8 bytes"); + let size: u64 = trx.rlp_len().try_into().expect("usize is 8 bytes"); let cost: u64 = ((size + (HOLDER_MSG_SIZE - 1)) / HOLDER_MSG_SIZE) .saturating_mul(WRITE_TO_HOLDER_TRX_COST); diff --git a/evm_loader/program/src/instruction/account_block_add.rs b/evm_loader/program/src/instruction/account_block_add.rs deleted file mode 100644 index 4d16feba9..000000000 --- a/evm_loader/program/src/instruction/account_block_add.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::collections::BTreeSet; - -use crate::account::{EthereumAccount, Operator, State}; -use crate::error::{Error, Result}; -use crate::state_account::BlockedAccountMeta; -use solana_program::instruction::TRANSACTION_LEVEL_STACK_HEIGHT; -use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; - -pub fn process<'a>( - program_id: &'a Pubkey, - accounts: &'a [AccountInfo<'a>], - _instruction: &[u8], -) -> Result<()> { - let stack_height = solana_program::instruction::get_stack_height(); - assert_eq!(stack_height, TRANSACTION_LEVEL_STACK_HEIGHT); - - solana_program::msg!("Instruction: Block Accounts"); - - let mut state = State::from_account(program_id, &accounts[0])?; - let operator = Operator::from_account(&accounts[1])?; - - if &state.owner != operator.key { - return Err(Error::HolderInvalidOwner(state.owner, *operator.key)); - } - - let mut blocked_accounts = state.read_blocked_accounts()?; - let mut blocked_keys: BTreeSet = blocked_accounts.iter().map(|a| a.key).collect(); - - for account_info in &accounts[2..] { - if blocked_keys.contains(account_info.key) { - continue; - } - - let mut meta = BlockedAccountMeta { - key: *account_info.key, - exists: false, - is_writable: account_info.is_writable, - }; - - if let Ok(mut account) = EthereumAccount::from_account(program_id, account_info) { - account.check_blocked()?; - account.rw_blocked = true; - - meta.exists = true; - } - - blocked_accounts.push(meta); - blocked_keys.insert(*account_info.key); - } - - state.update_blocked_accounts(blocked_accounts.into_iter()) -} diff --git a/evm_loader/program/src/instruction/account_create.rs b/evm_loader/program/src/instruction/account_create.rs deleted file mode 100644 index 75b7fb567..000000000 --- a/evm_loader/program/src/instruction/account_create.rs +++ /dev/null @@ -1,71 +0,0 @@ -use arrayref::array_ref; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; - -use crate::account::{program, EthereumAccount, Operator}; -use crate::types::Address; - -struct Accounts<'a> { - operator: Operator<'a>, - system_program: program::System<'a>, - ether_account: &'a AccountInfo<'a>, -} - -pub fn process<'a>( - program_id: &'a Pubkey, - accounts: &'a [AccountInfo<'a>], - instruction: &[u8], -) -> ProgramResult { - solana_program::msg!("Instruction: Create Account"); - - let parsed_accounts = Accounts { - operator: unsafe { Operator::from_account_not_whitelisted(&accounts[0]) }?, - system_program: program::System::from_account(&accounts[1])?, - ether_account: &accounts[2], - }; - - let address = array_ref![instruction, 0, 20]; - let address = Address::from(*address); - solana_program::msg!("Address: {}", address); - - let bump_seed = validate(program_id, &parsed_accounts, &address)?; - execute(program_id, &parsed_accounts, address, bump_seed) -} - -fn validate( - program_id: &Pubkey, - accounts: &Accounts, - address: &Address, -) -> Result { - if !solana_program::system_program::check_id(accounts.ether_account.owner) { - return Err!(ProgramError::InvalidArgument; "Account {} - expected system owned", accounts.ether_account.key); - } - - let (expected_address, bump_seed) = address.find_solana_address(program_id); - if expected_address != *accounts.ether_account.key { - return Err!(ProgramError::InvalidArgument; "Account {} - expected PDA address {}", accounts.ether_account.key, expected_address); - } - - Ok(bump_seed) -} - -fn execute( - program_id: &Pubkey, - accounts: &Accounts, - address: Address, - bump_seed: u8, -) -> ProgramResult { - EthereumAccount::create_and_init_account( - &accounts.system_program, - program_id, - &accounts.operator, - address, - accounts.ether_account, - bump_seed, - EthereumAccount::SIZE, - )?; - - Ok(()) -} diff --git a/evm_loader/program/src/instruction/account_create_balance.rs b/evm_loader/program/src/instruction/account_create_balance.rs new file mode 100644 index 000000000..8693310ab --- /dev/null +++ b/evm_loader/program/src/instruction/account_create_balance.rs @@ -0,0 +1,45 @@ +use arrayref::array_ref; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey, rent::Rent, sysvar::Sysvar}; + +use crate::account::{program, AccountsDB, BalanceAccount, Operator}; +use crate::config::{CHAIN_ID_LIST, DEFAULT_CHAIN_ID}; +use crate::error::{Error, Result}; +use crate::types::Address; + +pub fn process<'a>( + _program_id: &'a Pubkey, + accounts: &'a [AccountInfo<'a>], + instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Create Balance Account"); + + let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[0]) }?; + let system = program::System::from_account(&accounts[1])?; + + let accounts_db = AccountsDB::new(&accounts[2..], operator, None, Some(system), None); + + let address = array_ref![instruction, 0, 20]; + let address = Address::from(*address); + + let chain_id = array_ref![instruction, 20, 8]; + let chain_id = u64::from_le_bytes(*chain_id); + + CHAIN_ID_LIST + .binary_search_by_key(&chain_id, |c| c.0) + .map_err(|_| Error::InvalidChainId(chain_id))?; + + log_msg!("Address: {}, ChainID: {}", address, chain_id); + + let mut excessive_lamports = 0; + if chain_id == DEFAULT_CHAIN_ID { + // we don't have enough accounts to update non Neon chains + excessive_lamports += crate::account::legacy::update_legacy_accounts(&accounts_db)?; + }; + + let rent = Rent::get()?; + BalanceAccount::create(address, chain_id, &accounts_db, None, &rent)?; + + **accounts_db.operator().try_borrow_mut_lamports()? += excessive_lamports; + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/account_holder_create.rs b/evm_loader/program/src/instruction/account_holder_create.rs index d31c26381..ac6687d2b 100644 --- a/evm_loader/program/src/instruction/account_holder_create.rs +++ b/evm_loader/program/src/instruction/account_holder_create.rs @@ -1,26 +1,23 @@ use crate::account::{Holder, Operator}; use crate::error::Result; +use arrayref::array_ref; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; pub fn process<'a>( program_id: &'a Pubkey, accounts: &'a [AccountInfo<'a>], - _instruction: &[u8], + instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Create Holder Account"); + log_msg!("Instruction: Create Holder Account"); - let holder = &accounts[0]; + let holder = accounts[0].clone(); let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1]) }?; - Holder::init( - program_id, - holder, - crate::account::holder::Data { - owner: *operator.key, - transaction_hash: [0_u8; 32], - transaction_len: 0, - }, - )?; + let seed_len = usize::from_le_bytes(*array_ref![instruction, 0, 8]); + let seed_bytes = instruction[8..8 + seed_len].to_vec(); + let seed = std::str::from_utf8(&seed_bytes)?; + + Holder::create(program_id, holder, seed, &operator)?; Ok(()) } diff --git a/evm_loader/program/src/instruction/account_holder_delete.rs b/evm_loader/program/src/instruction/account_holder_delete.rs index da8af4ea3..6920b803b 100644 --- a/evm_loader/program/src/instruction/account_holder_delete.rs +++ b/evm_loader/program/src/instruction/account_holder_delete.rs @@ -1,5 +1,5 @@ -use crate::account::{FinalizedState, Holder, Operator}; -use crate::error::{Error, Result}; +use crate::account::{Holder, Operator}; +use crate::error::Result; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; pub fn process<'a>( @@ -7,32 +7,17 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], _instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Delete Holder Account"); + log_msg!("Instruction: Delete Holder Account"); + let holder_info = accounts[0].clone(); let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1]) }?; - match crate::account::tag(program_id, &accounts[0])? { - Holder::TAG => { - let holder = Holder::from_account(program_id, &accounts[0])?; - holder.validate_owner(&operator)?; + crate::account::legacy::update_holder_account(&holder_info)?; - unsafe { - holder.suicide(&operator); - } - } - FinalizedState::TAG => { - let finalized = FinalizedState::from_account(program_id, &accounts[0])?; - if &finalized.owner != operator.key { - return Err(Error::HolderInvalidOwner(finalized.owner, *operator.key)); - } - - unsafe { - finalized.suicide(&operator); - } - } - _ => { - return Err(Error::AccountInvalidTag(*accounts[0].key, Holder::TAG)); - } + let holder = Holder::from_account(program_id, holder_info)?; + holder.validate_owner(&operator)?; + unsafe { + holder.suicide(&operator); } Ok(()) diff --git a/evm_loader/program/src/instruction/account_holder_write.rs b/evm_loader/program/src/instruction/account_holder_write.rs index c305cc5c9..e9a5e8781 100644 --- a/evm_loader/program/src/instruction/account_holder_write.rs +++ b/evm_loader/program/src/instruction/account_holder_write.rs @@ -1,5 +1,6 @@ -use crate::account::{FinalizedState, Holder, Operator}; -use crate::error::{Error, Result}; +use crate::account::{Holder, Operator}; +use crate::debug::log_data; +use crate::error::Result; use arrayref::array_ref; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; @@ -8,40 +9,22 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Write To Holder"); + log_msg!("Instruction: Write To Holder"); let transaction_hash = *array_ref![instruction, 0, 32]; let offset = usize::from_le_bytes(*array_ref![instruction, 32, 8]); let data = &instruction[32 + 8..]; - let holder_info = &accounts[0]; - - let mut holder = match crate::account::tag(program_id, holder_info)? { - Holder::TAG => Holder::from_account(program_id, holder_info), - FinalizedState::TAG => { - let finalized_state = FinalizedState::from_account(program_id, holder_info)?; - let holder_data = crate::account::holder::Data { - owner: finalized_state.owner, - transaction_hash, - transaction_len: 0, - }; - unsafe { finalized_state.replace(holder_data) } - } - _ => { - return Err(Error::AccountInvalidTag(*holder_info.key, Holder::TAG)); - } - }?; - + let holder_info = accounts[0].clone(); let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1]) }?; - holder.validate_owner(&operator)?; + crate::account::legacy::update_holder_account(&holder_info)?; - if holder.transaction_hash != transaction_hash { - holder.clear()?; - holder.transaction_hash = transaction_hash; - } + let mut holder = Holder::from_account(program_id, holder_info)?; + holder.validate_owner(&operator)?; + holder.update_transaction_hash(transaction_hash); - solana_program::log::sol_log_data(&[b"HASH", &transaction_hash]); + log_data(&[b"HASH", &transaction_hash]); holder.write(offset, data)?; diff --git a/evm_loader/program/src/instruction/collect_treasury.rs b/evm_loader/program/src/instruction/collect_treasury.rs index bc950d378..af7547e26 100644 --- a/evm_loader/program/src/instruction/collect_treasury.rs +++ b/evm_loader/program/src/instruction/collect_treasury.rs @@ -13,7 +13,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> ProgramResult { - solana_program::msg!("Instruction: Collect treasury"); + log_msg!("Instruction: Collect treasury"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); diff --git a/evm_loader/program/src/instruction/config_get_chain_count.rs b/evm_loader/program/src/instruction/config_get_chain_count.rs new file mode 100644 index 000000000..d917b85a6 --- /dev/null +++ b/evm_loader/program/src/instruction/config_get_chain_count.rs @@ -0,0 +1,18 @@ +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::error::Result; + +pub fn process<'a>( + _program_id: &'a Pubkey, + _accounts: &'a [AccountInfo<'a>], + _instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Config Get Chain Count"); + + let count = crate::config::CHAIN_ID_LIST.len(); + + let return_data = count.to_le_bytes(); + solana_program::program::set_return_data(&return_data); + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/config_get_chain_info.rs b/evm_loader/program/src/instruction/config_get_chain_info.rs new file mode 100644 index 000000000..157539c8e --- /dev/null +++ b/evm_loader/program/src/instruction/config_get_chain_info.rs @@ -0,0 +1,20 @@ +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::error::Result; + +pub fn process<'a>( + _program_id: &'a Pubkey, + _accounts: &'a [AccountInfo<'a>], + instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Config Get Chain Info"); + + let bytes = instruction.try_into()?; + let index = usize::from_le_bytes(bytes); + let info = &crate::config::CHAIN_ID_LIST[index]; + + let return_data = bincode::serialize(info)?; + solana_program::program::set_return_data(&return_data); + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/config_get_environment.rs b/evm_loader/program/src/instruction/config_get_environment.rs new file mode 100644 index 000000000..cddb1edcd --- /dev/null +++ b/evm_loader/program/src/instruction/config_get_environment.rs @@ -0,0 +1,31 @@ +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::error::Result; + +pub fn process<'a>( + _program_id: &'a Pubkey, + _accounts: &'a [AccountInfo<'a>], + _instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Config Get Environment"); + + let environment: &str = if cfg!(feature = "mainnet") { + "mainnet" + } else if cfg!(feature = "testnet") { + "testnet" + } else if cfg!(feature = "devnet") { + "devnet" + } else if cfg!(feature = "govertest") { + "govertest" + } else if cfg!(feature = "ci") { + "ci" + } else if cfg!(feature = "rollup") { + "rollup" + } else { + "unknown" + }; + + solana_program::program::set_return_data(environment.as_bytes()); + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/config_get_property_by_index.rs b/evm_loader/program/src/instruction/config_get_property_by_index.rs new file mode 100644 index 000000000..ac9f62627 --- /dev/null +++ b/evm_loader/program/src/instruction/config_get_property_by_index.rs @@ -0,0 +1,20 @@ +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::error::Result; + +pub fn process<'a>( + _program_id: &'a Pubkey, + _accounts: &'a [AccountInfo<'a>], + instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Config Get Property by Index"); + + let bytes = instruction.try_into()?; + let index = usize::from_le_bytes(bytes); + let info = &crate::config::PARAMETERS[index]; + + let return_data = bincode::serialize(info)?; + solana_program::program::set_return_data(&return_data); + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/config_get_property_by_name.rs b/evm_loader/program/src/instruction/config_get_property_by_name.rs new file mode 100644 index 000000000..fdd09d4ac --- /dev/null +++ b/evm_loader/program/src/instruction/config_get_property_by_name.rs @@ -0,0 +1,25 @@ +use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; + +use crate::config::PARAMETERS; +use crate::error::Result; + +pub fn process<'a>( + _program_id: &'a Pubkey, + _accounts: &'a [AccountInfo<'a>], + instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Config Get Property by Name"); + + let requested_property = std::str::from_utf8(instruction)?; + + let Ok(index) = PARAMETERS.binary_search_by(|p| p.0.cmp(requested_property)) else { + return Err(ProgramError::InvalidArgument.into()); + }; + + let (name, value) = PARAMETERS[index]; + assert_eq!(requested_property, name); + + solana_program::program::set_return_data(value.as_bytes()); + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/config_get_property_count.rs b/evm_loader/program/src/instruction/config_get_property_count.rs new file mode 100644 index 000000000..2732f22f2 --- /dev/null +++ b/evm_loader/program/src/instruction/config_get_property_count.rs @@ -0,0 +1,18 @@ +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::error::Result; + +pub fn process<'a>( + _program_id: &'a Pubkey, + _accounts: &'a [AccountInfo<'a>], + _instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Config Get Property Count"); + + let count = crate::config::PARAMETERS.len(); + + let return_data = count.to_le_bytes(); + solana_program::program::set_return_data(&return_data); + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/config_get_status.rs b/evm_loader/program/src/instruction/config_get_status.rs new file mode 100644 index 000000000..158e2c46d --- /dev/null +++ b/evm_loader/program/src/instruction/config_get_status.rs @@ -0,0 +1,19 @@ +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::error::Result; + +pub fn process<'a>( + _program_id: &'a Pubkey, + _accounts: &'a [AccountInfo<'a>], + _instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Config Get Status"); + + if cfg!(feature = "emergency") { + solana_program::program::set_return_data(&[0]); + } else { + solana_program::program::set_return_data(&[1]); + } + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/config_get_version.rs b/evm_loader/program/src/instruction/config_get_version.rs new file mode 100644 index 000000000..9d0b0dc1a --- /dev/null +++ b/evm_loader/program/src/instruction/config_get_version.rs @@ -0,0 +1,22 @@ +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::{ + config::{NEON_PKG_VERSION, NEON_REVISION}, + error::Result, +}; + +pub fn process<'a>( + _program_id: &'a Pubkey, + _accounts: &'a [AccountInfo<'a>], + _instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Config Get Version"); + + let version = std::str::from_utf8(&NEON_PKG_VERSION)?; + let revision = std::str::from_utf8(&NEON_REVISION)?; + + let return_data = bincode::serialize(&(version, revision))?; + solana_program::program::set_return_data(&return_data); + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/create_main_treasury.rs b/evm_loader/program/src/instruction/create_main_treasury.rs index 172b36d30..1378551ad 100644 --- a/evm_loader/program/src/instruction/create_main_treasury.rs +++ b/evm_loader/program/src/instruction/create_main_treasury.rs @@ -1,16 +1,16 @@ use crate::{ account::{program::System, program::Token, MainTreasury, Operator}, config::TREASURY_POOL_SEED, + error::{Error, Result}, }; use solana_program::{ account_info::AccountInfo, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, - entrypoint::ProgramResult, - msg, - program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, + rent::Rent, system_program, + sysvar::Sysvar, }; struct Accounts<'a> { @@ -24,7 +24,7 @@ struct Accounts<'a> { } impl<'a> Accounts<'a> { - pub fn from_slice(accounts: &'a [AccountInfo<'a>]) -> Result, ProgramError> { + pub fn from_slice(accounts: &'a [AccountInfo<'a>]) -> Result> { Ok(Accounts { main_treasury: &accounts[0], program_data: &accounts[1], @@ -40,26 +40,26 @@ impl<'a> Accounts<'a> { fn get_program_upgrade_authority<'a>( program_id: &'a Pubkey, program_data: &'a AccountInfo<'a>, -) -> Result { +) -> Result { let (expected_program_data_key, _) = Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id()); if *program_data.key != expected_program_data_key { - return Err!(ProgramError::InvalidArgument; "Account {} - invalid current program data account", program_data.key); + return Err(Error::AccountInvalidKey( + *program_data.key, + expected_program_data_key, + )); } - let unpacked_program_data: UpgradeableLoaderState = bincode::deserialize( - &program_data.data.borrow(), - ) - .map_err(|_| E!(ProgramError::InvalidAccountData; "Unable to deserialize program data"))?; + let unpacked_program_data: UpgradeableLoaderState = + bincode::deserialize(&program_data.data.borrow())?; let upgrade_authority: Pubkey = match unpacked_program_data { UpgradeableLoaderState::ProgramData { slot: _, upgrade_authority_address, - } => upgrade_authority_address - .ok_or_else(|| E!(ProgramError::InvalidAccountData; "Not upgradeable program" ))?, - _ => return Err!(ProgramError::InvalidAccountData; "Not ProgramData"), + } => upgrade_authority_address.ok_or_else(|| Error::from("Not upgradeable program"))?, + _ => return Err(Error::from("Not ProgramData")), }; Ok(upgrade_authority) @@ -69,35 +69,50 @@ pub fn process<'a>( program_id: &'a Pubkey, accounts: &'a [AccountInfo<'a>], _instruction: &[u8], -) -> ProgramResult { - msg!("Instruction: Create Main Treasury"); +) -> Result<()> { + log_msg!("Instruction: Create Main Treasury"); let accounts = Accounts::from_slice(accounts)?; let (expected_key, bump_seed) = MainTreasury::address(program_id); if *accounts.main_treasury.key != expected_key { - return Err!(ProgramError::InvalidArgument; "Account {} - invalid main treasure account", accounts.main_treasury.key); + return Err(Error::AccountInvalidKey( + *accounts.main_treasury.key, + expected_key, + )); } if *accounts.mint.key != spl_token::native_mint::id() { - return Err!(ProgramError::InvalidArgument; "Account {} - not wrapped SOL mint", accounts.mint.key); + return Err(Error::Custom(std::format!( + "Account {} - not wrapped SOL mint", + accounts.mint.key + ))); } if *accounts.system_program.key != system_program::id() { - return Err!(ProgramError::InvalidArgument; "Account {} - not system program", accounts.system_program.key); + return Err(Error::AccountInvalidKey( + *accounts.system_program.key, + system_program::id(), + )); } if *accounts.token_program.key != spl_token::id() { - return Err!(ProgramError::InvalidArgument; "Account {} - not spl-token program", accounts.token_program.key); + return Err(Error::AccountInvalidKey( + *accounts.token_program.key, + spl_token::id(), + )); } let expected_upgrade_auth_key = get_program_upgrade_authority(program_id, accounts.program_data)?; if *accounts.program_upgrade_auth.key != expected_upgrade_auth_key { - return Err!(ProgramError::InvalidArgument; "Account {} - invalid program upgrade authority", accounts.program_upgrade_auth.key); + return Err(Error::AccountInvalidKey( + *accounts.program_upgrade_auth.key, + expected_upgrade_auth_key, + )); } if !accounts.program_upgrade_auth.is_signer { - return Err!(ProgramError::MissingRequiredSignature; "Required signature from program upgrade authority"); + return Err(Error::AccountNotSigner(*accounts.program_upgrade_auth.key)); } accounts.system_program.create_pda_account( @@ -106,6 +121,7 @@ pub fn process<'a>( accounts.main_treasury, &[TREASURY_POOL_SEED.as_bytes(), &[bump_seed]], spl_token::state::Account::LEN, + &Rent::get()?, )?; accounts.token_program.create_account( diff --git a/evm_loader/program/src/instruction/mod.rs b/evm_loader/program/src/instruction/mod.rs index 63f45e32f..33cafddfe 100644 --- a/evm_loader/program/src/instruction/mod.rs +++ b/evm_loader/program/src/instruction/mod.rs @@ -6,75 +6,209 @@ use solana_program::program_error::ProgramError; /// `EvmInstruction` serialized in instruction data #[derive(Debug, PartialEq, Eq, Clone)] pub enum EvmInstruction { - /// Deposits NEON tokens to an Ether account (V3). + /// Deposits spl-tokens to an Ether account. /// Requires previously executed SPL-Token.Approve which /// delegates the deposit amount to the NEON destination account. /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable]` NEON token source account. - /// 1. `[writable]` NEON token pool (destination) account. - /// 2. `[writable]` Ether account to store balance of NEONs. - /// 3. `[]` SPL Token program id. - /// 4. `[writeable,signer]` Funding account (must be a system account). - /// 5. `[]` System program. - DepositV03, - - /// Create Ethereum account V3 - /// # Account references - /// 0. [WRITE, SIGNER] Funding account - /// 1. [] System Program - /// 2. [WRITE] New account (program_address(version, ether, bump_seed)) - CreateAccountV03, + /// Accounts: + /// `[]` spl-token mint account. + /// `[WRITE]` spl-token source account. + /// `[WRITE]` spl-token pool (destination) account. + /// `[WRITE]` NeonEVM user balance account + /// `[WRITE]` NeonEVM user contract account + /// `[]` SPL Token program id. + /// `[writeable,signer]` Funding account (must be a system account). + /// `[]` System program. + /// Instruction data: + /// 0..20 - destination address + /// 20..28 - chain id in little endian + Deposit, /// Collect lamports from treasury pool accounts to main pool balance - /// 0. `[WRITE]` Main treasury balance: PDA["treasury_pool"] - /// 1. `[WRITE]` Auxiliary treasury balance: PDA["treasury_pool", index.to_le_bytes()] - /// 2. `[]` System program + /// + /// Accounts: + /// `[WRITE]` Main treasury balance: PDA["treasury_pool"] + /// `[WRITE]` Auxiliary treasury balance: PDA["treasury_pool", index.to_le_bytes()] + /// `[]` System program + /// Instruction data: + /// 0..4 - treasury index in little endian CollectTreasure, /// Create Holder Account + /// + /// Accounts: + /// `[WRITE]` Holder Account + /// `[SIGNER]` Holder Account Owner + /// Instruction data: + /// 0..8 - seed length in little endian + /// 8..8+seed_len - seed in utf-8 HolderCreate, /// Delete Holder Account + /// + /// Accounts: + /// `[WRITE]` Holder Account + /// `[WRITE,SIGNER]` Holder Account Owner + /// Instruction data: + /// None HolderDelete, /// Write Transaction into Holder Account + /// + /// Accounts: + /// `[WRITE]` Holder Account + /// `[SIGNER]` Holder Account Owner + /// Instruction data: + /// 0..32 - transaction hash + /// 32..40 - offset in Holder in little endian + /// 40.. - transaction data HolderWrite, /// Execute Transaction from Instruction in single iteration + /// + /// Accounts: + /// `[WRITE]` Holder + /// `[WRITE,SIGNER]` Operator + /// `[WRITE]` Treasury + /// `[WRITE]` Operator Balance + /// `[]` System program + /// `[WRITE?]` Other accounts + /// Instruction data: + /// 0..4 - treasury index in little endian + /// 4.. - transaction data TransactionExecuteFromInstruction, /// Execute Transaction from Account in single iteration + /// + /// Accounts: + /// `[WRITE]` Holder + /// `[WRITE,SIGNER]` Operator + /// `[WRITE]` Treasury + /// `[WRITE]` Operator Balance + /// `[]` System program + /// `[WRITE?]` Other accounts + /// Instruction data: + /// 0..4 - treasury index in little endian TransactionExecuteFromAccount, /// Execute Iterative Transaction from Instruction + /// + /// Accounts: + /// `[WRITE]` Holder/State + /// `[WRITE,SIGNER]` Operator + /// `[WRITE]` Treasury + /// `[WRITE]` Operator Balance + /// `[]` System program + /// `[WRITE]` Other accounts + /// Instruction data: + /// 0..4 - treasury index in little endian + /// 4..8 - step count in little endian + /// 8.. - transaction data TransactionStepFromInstruction, /// Execute Iterative Transaction from Account + /// + /// Accounts: + /// `[WRITE]` Holder/State + /// `[WRITE,SIGNER]` Operator + /// `[WRITE]` Treasury + /// `[WRITE]` Operator Balance + /// `[]` System program + /// `[WRITE]` Other accounts + /// Instruction data: + /// 0..4 - treasury index in little endian + /// 4..8 - step count in little endian TransactionStepFromAccount, /// Execute Iterative Transaction without ChainId from Account + /// + /// Accounts: + /// `[WRITE]` Holder/State + /// `[WRITE,SIGNER]` Operator + /// `[WRITE]` Treasury + /// `[WRITE]` Operator Balance + /// `[]` System program + /// `[WRITE]` Other accounts + /// Instruction data: + /// 0..4 - treasury index in little endian + /// 4..8 - step count in little endian TransactionStepFromAccountNoChainId, /// Cancel Transaction + /// + /// Accounts: + /// `[WRITE]` State + /// `[SIGNER]` Operator + /// `[WRITE]` Operator Balance + /// Instruction data: + /// 0..32 - transaction hash Cancel, /// CreateMainTreasury - /// 0. `[WRITE]` Main treasury balance: PDA["treasury_pool"] - /// 1. `[]` Program data (to get program upgrade-authority) - /// 2. `[SIGNER]` Owner for account (upgrade program authority) - /// 3. `[]` SPL token program id - /// 4. `[]` System program - /// 5. `[]` wSOL mint - /// 6. `[WRITE,SIGNER]` Payer + /// + /// Accounts: + /// `[WRITE]` Main treasury balance: PDA["treasury_pool"] + /// `[]` Program data (to get program upgrade-authority) + /// `[SIGNER]` Owner for account (upgrade program authority) + /// `[]` SPL token program id + /// `[]` System program + /// `[]` wSOL mint + /// `[WRITE,SIGNER]` Payer + /// Instruction data: + /// None CreateMainTreasury, - /// Block additional accounts - AccountBlockAdd, + /// Create a User Balance account + /// + /// Accounts: + /// `[WRITE,SIGNER]` Operator + /// `[]` System program + /// `[WRITE]` NeonEVM user balance account + /// `[WRITE]` NeonEVM user contract account + /// Instruction data: + /// 0..20 - address + /// 20..28 - chain id in little endian + AccountCreateBalance, + + /// Execute Transaction from Instruction in a single iteration with a call to Solana programs + /// + /// Accounts: + /// `[WRITE]` Holder + /// `[WRITE,SIGNER]` Operator + /// `[WRITE]` Treasury + /// `[WRITE]` Operator Balance + /// `[]` System program + /// `[WRITE?]` Other accounts + /// Instruction data: + /// 0..4 - treasury index in little-endian + /// 4.. - transaction data + TransactionExecuteFromInstructionWithSolanaCall, + + /// Execute Transaction from Account in a single iteration + /// + /// Accounts: + /// `[WRITE]` Holder + /// `[WRITE,SIGNER]` Operator + /// `[WRITE]` Treasury + /// `[WRITE]` Operator Balance + /// `[]` System program + /// `[WRITE?]` Other accounts + /// Instruction data: + /// 0..4 - treasury index in little-endian + TransactionExecuteFromAccountWithSolanaCall, - /// Modify account's nonce. Only for test environment. - TestAccountUpdateNonce, + ConfigGetChainCount, + ConfigGetChainInfo, + ConfigGetEnvironment, + ConfigGetPropertyCount, + ConfigGetPropertyByIndex, + ConfigGetPropertyByName, + ConfigGetStatus, + ConfigGetVersion, + + OperatorBalanceCreate, + OperatorBalanceDelete, + OperatorBalanceWithdraw, } impl EvmInstruction { @@ -84,42 +218,67 @@ impl EvmInstruction { /// Will return `ProgramError::InvalidInstructionData` if can't parse `tag` pub const fn parse(tag: &u8) -> Result { Ok(match tag { - 0x1e => Self::CollectTreasure, // 30 - 0x1f => Self::TransactionExecuteFromInstruction, // 31 - 0x20 => Self::TransactionStepFromInstruction, // 32 - 0x21 => Self::TransactionStepFromAccount, // 33 - 0x22 => Self::TransactionStepFromAccountNoChainId, // 34 - 0x23 => Self::Cancel, // 35 - 0x24 => Self::HolderCreate, // 36 - 0x25 => Self::HolderDelete, // 37 - 0x26 => Self::HolderWrite, // 38 - 0x27 => Self::DepositV03, // 39 - 0x28 => Self::CreateAccountV03, // 40 - 0x29 => Self::CreateMainTreasury, // 41 - 0x2A => Self::TransactionExecuteFromAccount, // 42 - 0x2B => Self::AccountBlockAdd, // 43 - 0x2C => Self::TestAccountUpdateNonce, // 44 + 0x1e => Self::CollectTreasure, // 30 + 0x24 => Self::HolderCreate, // 36 + 0x25 => Self::HolderDelete, // 37 + 0x26 => Self::HolderWrite, // 38 + 0x29 => Self::CreateMainTreasury, // 41 + + 0x30 => Self::AccountCreateBalance, // 48 + 0x31 => Self::Deposit, // 49 + 0x3D => Self::TransactionExecuteFromInstruction, // 61 + 0x33 => Self::TransactionExecuteFromAccount, // 51 + 0x34 => Self::TransactionStepFromInstruction, // 52 + 0x35 => Self::TransactionStepFromAccount, // 53 + 0x36 => Self::TransactionStepFromAccountNoChainId, // 54 + 0x37 => Self::Cancel, // 55 + 0x3E => Self::TransactionExecuteFromInstructionWithSolanaCall, // 62 + 0x39 => Self::TransactionExecuteFromAccountWithSolanaCall, // 57 + + 0x3A => Self::OperatorBalanceCreate, // 58 + 0x3B => Self::OperatorBalanceDelete, // 59 + 0x3C => Self::OperatorBalanceWithdraw, // 60 + + 0xA0 => Self::ConfigGetChainCount, // 160 + 0xA1 => Self::ConfigGetChainInfo, + 0xA2 => Self::ConfigGetEnvironment, + 0xA3 => Self::ConfigGetPropertyCount, + 0xA4 => Self::ConfigGetPropertyByIndex, + 0xA5 => Self::ConfigGetPropertyByName, + 0xA6 => Self::ConfigGetStatus, + 0xA7 => Self::ConfigGetVersion, _ => return Err(ProgramError::InvalidInstructionData), }) } } -pub mod account_block_add; -pub mod account_create; +pub mod account_create_balance; pub mod account_holder_create; pub mod account_holder_delete; pub mod account_holder_write; pub mod collect_treasury; +pub mod config_get_chain_count; +pub mod config_get_chain_info; +pub mod config_get_environment; +pub mod config_get_property_by_index; +pub mod config_get_property_by_name; +pub mod config_get_property_count; +pub mod config_get_status; +pub mod config_get_version; pub mod create_main_treasury; pub mod neon_tokens_deposit; +pub mod operator_create_balance; +pub mod operator_delete_balance; +pub mod operator_withdraw_balance; +pub mod priority_fee_txn_calculator; pub mod transaction_cancel; pub mod transaction_execute; pub mod transaction_execute_from_account; +pub mod transaction_execute_from_account_solana_call; pub mod transaction_execute_from_instruction; +pub mod transaction_execute_from_instruction_solana_call; pub mod transaction_step; pub mod transaction_step_from_account; pub mod transaction_step_from_account_no_chainid; pub mod transaction_step_from_instruction; - -pub mod test_account_update_nonce; diff --git a/evm_loader/program/src/instruction/neon_tokens_deposit.rs b/evm_loader/program/src/instruction/neon_tokens_deposit.rs index 0ae014f8f..3c0fc3962 100644 --- a/evm_loader/program/src/instruction/neon_tokens_deposit.rs +++ b/evm_loader/program/src/instruction/neon_tokens_deposit.rs @@ -1,19 +1,20 @@ use arrayref::array_ref; use ethnum::U256; use solana_program::program::invoke_signed; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey, rent::Rent, sysvar::Sysvar}; use spl_associated_token_account::get_associated_token_address; -use crate::account::{program, token, EthereumAccount, Operator, ACCOUNT_SEED_VERSION}; +use crate::account::{program, token, AccountsDB, BalanceAccount, Operator, ACCOUNT_SEED_VERSION}; +use crate::config::{CHAIN_ID_LIST, DEFAULT_CHAIN_ID}; +use crate::error::{Error, Result}; use crate::types::Address; struct Accounts<'a> { + mint: token::Mint<'a>, source: token::State<'a>, pool: token::State<'a>, - ethereum_account: &'a AccountInfo<'a>, + balance_account: &'a AccountInfo<'a>, + contract_account: &'a AccountInfo<'a>, token_program: program::Token<'a>, operator: Operator<'a>, system_program: program::System<'a>, @@ -22,14 +23,16 @@ struct Accounts<'a> { const AUTHORITY_SEED: &[u8] = b"Deposit"; impl<'a> Accounts<'a> { - pub fn from_slice(accounts: &'a [AccountInfo<'a>]) -> Result, ProgramError> { + pub fn from_slice(accounts: &'a [AccountInfo<'a>]) -> Result> { Ok(Accounts { - source: token::State::from_account(&accounts[0])?, - pool: token::State::from_account(&accounts[1])?, - ethereum_account: &accounts[2], - token_program: program::Token::from_account(&accounts[3])?, - operator: unsafe { Operator::from_account_not_whitelisted(&accounts[4]) }?, - system_program: program::System::from_account(&accounts[5])?, + mint: token::Mint::from_account(&accounts[0])?, + source: token::State::from_account(&accounts[1])?, + pool: token::State::from_account(&accounts[2])?, + balance_account: &accounts[3], + contract_account: &accounts[4], + token_program: program::Token::from_account(&accounts[5])?, + operator: unsafe { Operator::from_account_not_whitelisted(&accounts[6]) }?, + system_program: program::System::from_account(&accounts[7])?, }) } } @@ -38,106 +41,91 @@ pub fn process<'a>( program_id: &'a Pubkey, accounts: &'a [AccountInfo<'a>], instruction: &[u8], -) -> ProgramResult { - solana_program::msg!("Instruction: Deposit"); +) -> Result<()> { + log_msg!("Instruction: Deposit"); let parsed_accounts = Accounts::from_slice(accounts)?; - let ethereum_address = Address::from(*array_ref![instruction, 0, 20]); - - let ethereum_bump_seed = validate(program_id, &parsed_accounts, ðereum_address)?; - execute( - program_id, - &parsed_accounts, - ethereum_address, - ethereum_bump_seed, - ) + + let address = array_ref![instruction, 0, 20]; + let address = Address::from(*address); + + let chain_id = array_ref![instruction, 20, 8]; + let chain_id = u64::from_le_bytes(*chain_id); + + validate(program_id, &parsed_accounts, address, chain_id)?; + execute(program_id, parsed_accounts, address, chain_id) } fn validate( program_id: &Pubkey, accounts: &Accounts, - ethereum_address: &Address, -) -> Result { - let (expected_solana_address, ethereum_bump_seed) = - ethereum_address.find_solana_address(program_id); - if expected_solana_address != *accounts.ethereum_account.key { - return Err!( - ProgramError::InvalidArgument; - "Account {} - expected PDA address {}", - accounts.ethereum_account.key, - expected_solana_address - ); + address: Address, + chain_id: u64, +) -> Result<()> { + let balance_account = *accounts.balance_account.key; + let contract_account = *accounts.contract_account.key; + let pool = *accounts.pool.info.key; + let mint = *accounts.mint.info.key; + + let (expected_pubkey, _) = address.find_balance_address(program_id, chain_id); + if expected_pubkey != balance_account { + return Err(Error::AccountInvalidKey(balance_account, expected_pubkey)); } - if accounts.source.mint != crate::config::token_mint::id() { - return Err!( - ProgramError::InvalidArgument; - "Account {} - expected Neon Token account", - accounts.source.info.key - ); + let (expected_pubkey, _) = address.find_solana_address(program_id); + if expected_pubkey != contract_account { + return Err(Error::AccountInvalidKey(contract_account, expected_pubkey)); + } + + let Ok(chain_id_index) = CHAIN_ID_LIST.binary_search_by_key(&chain_id, |c| c.0) else { + return Err(Error::InvalidChainId(chain_id)); + }; + + let expected_mint = CHAIN_ID_LIST[chain_id_index].2; + if mint != expected_mint { + return Err(Error::AccountInvalidKey(mint, expected_mint)); } let (authority_address, _) = Pubkey::find_program_address(&[AUTHORITY_SEED], program_id); - let expected_pool_address = - get_associated_token_address(&authority_address, &crate::config::token_mint::id()); - - if accounts.pool.info.key != &expected_pool_address { - return Err!( - ProgramError::InvalidArgument; - "Account {} - expected Neon Token Pool {}", - accounts.pool.info.key, - expected_pool_address - ); + let expected_pool = get_associated_token_address(&authority_address, &mint); + if pool != expected_pool { + return Err(Error::AccountInvalidKey(pool, expected_pool)); } - if accounts.pool.mint != crate::config::token_mint::id() { - return Err!( - ProgramError::InvalidArgument; - "Account {} - expected Neon Token account", - accounts.pool.info.key - ); + if (accounts.pool.mint != mint) || (accounts.source.mint != mint) { + return Err(Error::from("Invalid token mint")); } - if !accounts + let is_correct_delegate = accounts .source .delegate - .contains(accounts.ethereum_account.key) - { - return Err!( - ProgramError::InvalidArgument; - "Account {} - expected tokens delegated to an user account", - accounts.source.info.key - ); + .contains(accounts.balance_account.key); + + if !is_correct_delegate { + return Err(Error::from("Expected tokens delegated to balance account")); } if accounts.source.delegated_amount < 1 { - return Err!( - ProgramError::InvalidArgument; - "Account {} - expected positive tokens amount delegated to an user account", - accounts.source.info.key - ); + return Err(Error::from("Expected positive tokens amount delegated")); } - Ok(ethereum_bump_seed) + Ok(()) } -fn execute( - program_id: &Pubkey, - accounts: &Accounts, - ethereum_address: Address, - ethereum_bump_seed: u8, -) -> ProgramResult { - let signers_seeds: &[&[&[u8]]] = &[&[ +fn execute(program_id: &Pubkey, accounts: Accounts, address: Address, chain_id: u64) -> Result<()> { + let (_, bump_seed) = address.find_balance_address(program_id, chain_id); + let signer_seeds: &[&[u8]] = &[ &[ACCOUNT_SEED_VERSION], - ethereum_address.as_bytes(), - &[ethereum_bump_seed], - ]]; + address.as_bytes(), + &U256::from(chain_id).to_be_bytes(), + &[bump_seed], + ]; let instruction = spl_token::instruction::transfer( accounts.token_program.key, accounts.source.info.key, accounts.pool.info.key, - accounts.ethereum_account.key, + accounts.balance_account.key, &[], accounts.source.delegated_amount, )?; @@ -145,38 +133,42 @@ fn execute( let account_infos: &[AccountInfo] = &[ accounts.source.info.clone(), accounts.pool.info.clone(), - accounts.ethereum_account.clone(), + accounts.balance_account.clone(), accounts.token_program.clone(), ]; - invoke_signed(&instruction, account_infos, signers_seeds)?; - - if solana_program::system_program::check_id(accounts.ethereum_account.owner) { - EthereumAccount::create_and_init_account( - &accounts.system_program, - program_id, - &accounts.operator, - ethereum_address, - accounts.ethereum_account, - ethereum_bump_seed, - EthereumAccount::SIZE, - )?; - } + invoke_signed(&instruction, account_infos, &[signer_seeds])?; - let additional_decimals: u32 = (18 - crate::config::token_mint::decimals()).into(); + let token_decimals = accounts.mint.decimals; + assert!(token_decimals <= 18); + + let additional_decimals: u32 = (18 - token_decimals).into(); let deposit = U256::from(accounts.source.delegated_amount) * 10_u128.pow(additional_decimals); - let mut ethereum_account = - EthereumAccount::from_account(program_id, accounts.ethereum_account)?; - ethereum_account.balance = ethereum_account - .balance - .checked_add(deposit) - .ok_or_else(|| { - E!( - ProgramError::InvalidArgument; - "Account {} - balance overflow", - ethereum_address - ) - })?; + + let accounts_db = AccountsDB::new( + &[ + accounts.balance_account.clone(), + accounts.contract_account.clone(), + ], + accounts.operator, + None, + Some(accounts.system_program), + None, + ); + + let mut excessive_lamports = 0; + if chain_id == DEFAULT_CHAIN_ID { + // we don't have enough accounts to update non Neon chains + excessive_lamports += crate::account::legacy::update_legacy_accounts(&accounts_db)?; + } + + let rent = Rent::get()?; + + let mut balance_account = BalanceAccount::create(address, chain_id, &accounts_db, None, &rent)?; + balance_account.increment_revision(&rent, &accounts_db)?; + balance_account.mint(deposit)?; + + **accounts_db.operator().try_borrow_mut_lamports()? += excessive_lamports; Ok(()) } diff --git a/evm_loader/program/src/instruction/operator_create_balance.rs b/evm_loader/program/src/instruction/operator_create_balance.rs new file mode 100644 index 000000000..fa3a8d10e --- /dev/null +++ b/evm_loader/program/src/instruction/operator_create_balance.rs @@ -0,0 +1,36 @@ +use arrayref::array_ref; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey, rent::Rent, sysvar::Sysvar}; + +use crate::account::{program, Operator, OperatorBalanceAccount}; +use crate::config::CHAIN_ID_LIST; +use crate::error::{Error, Result}; +use crate::types::Address; + +pub fn process<'a>( + _program_id: &'a Pubkey, + accounts: &'a [AccountInfo<'a>], + instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Create Operator Balance Account"); + + let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[0]) }?; + let system = program::System::from_account(&accounts[1])?; + let account = &accounts[2]; + + let address = array_ref![instruction, 0, 20]; + let address = Address::from(*address); + + let chain_id = array_ref![instruction, 20, 8]; + let chain_id = u64::from_le_bytes(*chain_id); + + CHAIN_ID_LIST + .binary_search_by_key(&chain_id, |c| c.0) + .map_err(|_| Error::InvalidChainId(chain_id))?; + + log_msg!("Address: {}, ChainID: {}", address, chain_id); + + let rent = Rent::get()?; + OperatorBalanceAccount::create(address, chain_id, account, &operator, &system, &rent)?; + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/operator_delete_balance.rs b/evm_loader/program/src/instruction/operator_delete_balance.rs new file mode 100644 index 000000000..2b0ee3669 --- /dev/null +++ b/evm_loader/program/src/instruction/operator_delete_balance.rs @@ -0,0 +1,22 @@ +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::account::{Operator, OperatorBalanceAccount}; +use crate::error::Result; + +pub fn process<'a>( + program_id: &'a Pubkey, + accounts: &'a [AccountInfo<'a>], + _instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Delete Operator Balance Account"); + + let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[0]) }?; + let operator_balance = OperatorBalanceAccount::from_account(program_id, &accounts[1])?; + + operator_balance.validate_owner(&operator)?; + unsafe { + operator_balance.suicide(&operator); + } + + Ok(()) +} diff --git a/evm_loader/program/src/instruction/operator_withdraw_balance.rs b/evm_loader/program/src/instruction/operator_withdraw_balance.rs new file mode 100644 index 000000000..be11dc7af --- /dev/null +++ b/evm_loader/program/src/instruction/operator_withdraw_balance.rs @@ -0,0 +1,26 @@ +use solana_program::rent::Rent; +use solana_program::sysvar::Sysvar; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::account::program::System; +use crate::account::{AccountsDB, BalanceAccount, Operator, OperatorBalanceAccount}; +use crate::error::Result; + +pub fn process<'a>( + program_id: &'a Pubkey, + accounts: &'a [AccountInfo<'a>], + _instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Withdraw Operator Balance Account"); + + let system = System::from_account(&accounts[0])?; + let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1]) }?; + let mut operator_balance = OperatorBalanceAccount::from_account(program_id, &accounts[2])?; + let mut target_balance = BalanceAccount::from_account(program_id, accounts[3].clone())?; + + operator_balance.validate_owner(&operator)?; + operator_balance.withdraw(&mut target_balance)?; + + let accounts_db = AccountsDB::new(&[], operator, Some(operator_balance), Some(system), None); + target_balance.increment_revision(&Rent::get()?, &accounts_db) +} diff --git a/evm_loader/program/src/instruction/priority_fee_txn_calculator.rs b/evm_loader/program/src/instruction/priority_fee_txn_calculator.rs new file mode 100644 index 000000000..eec143b14 --- /dev/null +++ b/evm_loader/program/src/instruction/priority_fee_txn_calculator.rs @@ -0,0 +1,138 @@ +use crate::debug::log_data; +use crate::gasometer::LAMPORTS_PER_SIGNATURE; +use crate::types::Transaction; +use crate::types::TransactionPayload; +use crate::{error::Error, types::DynamicFeeTx}; +use ethnum::U256; +use solana_program::{instruction::get_processed_sibling_instruction, pubkey, pubkey::Pubkey}; +use std::convert::From; + +// Because ComputeBudget program is not accessible through CPI, it's not a part of the standard +// solana_program library crate. Thus, we have to hardcode a couple of constants. +// The pubkey of the Compute Budget. +const COMPUTE_BUDGET_ADDRESS: Pubkey = pubkey!("ComputeBudget111111111111111111111111111111"); +// The Compute Budget SetComputeUnitLimit instruction tag. +const COMPUTE_UNIT_LIMIT_TAG: u8 = 0x2; +// The Compute Budget SetComputeUnitPrice instruction tag. +const COMPUTE_UNIT_PRICE_TAG: u8 = 0x3; +// The default compute units limit for Solana transactions. +const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 200_000; + +// Conversion from "total micro lamports" to lamports per gas unit. +const CONVERSION_MULTIPLIER: u64 = 1_000_000 / LAMPORTS_PER_SIGNATURE; + +/// Handles priority fee: +/// - No-op for anything but DynamicFee transactions, +/// - Calculates and logs the priority fee in tokens for DynamicFee transactions. +pub fn handle_priority_fee(txn: &Transaction, gas_amount: U256) -> Result { + if let TransactionPayload::DynamicFee(ref dynamic_fee_payload) = txn.transaction { + let priority_fee_in_tokens = get_priority_fee_in_tokens(dynamic_fee_payload, gas_amount)?; + log_data(&[b"PRIORITYFEE", &priority_fee_in_tokens.to_le_bytes()]); + return Ok(priority_fee_in_tokens); + } + Ok(U256::ZERO) +} + +/// Returns the amount of "priority fee in tokens" that User have to pay to the Operator. +pub fn get_priority_fee_in_tokens(txn: &DynamicFeeTx, gas_amount: U256) -> Result { + let max_fee = txn.max_fee_per_gas; + let max_priority_fee = txn.max_priority_fee_per_gas; + + if max_priority_fee > max_fee { + return Err(Error::PriorityFeeError( + "max_priority_fee_per_gas > max_fee_per_gas".to_string(), + )); + } + + if max_fee == max_priority_fee { + // If max_fee_per_gas == max_priority_fee_per_gas, we handle transaction as legacy: + // - charge max_fee_per_gas * gas_used, + // - do not charge any priority fee. + return Ok(U256::ZERO); + } + + if max_priority_fee == U256::ZERO { + // If the User set priority fee to zero, the resulting priority fee is 0. + return Ok(U256::ZERO); + } + + let (cu_limit, cu_price) = get_compute_budget_priority_fee()?; + + let priority_fee_per_gas_in_lamports: u64 = cu_price + .checked_mul(CONVERSION_MULTIPLIER * cu_limit as u64) + .ok_or(Error::PriorityFeeError( + "cu_limit * cu_price overflow".to_string(), + ))?; + let base_fee_per_gas = max_fee - max_priority_fee; + + // Get minimum value of priority_fee_per_gas from what the User sets as max_priority_fee_per_gas + // and what the operator paid as Compute Budget (as converted to gas tokens). + Ok( + max_priority_fee.min(base_fee_per_gas * U256::from(priority_fee_per_gas_in_lamports)) + * gas_amount, + ) +} + +/// Extracts the data about compute units from instructions within the current transaction. +/// Returns the pair of (`compute_budget_unit_limit`, `compute_budget_unit_price`) +/// N.B. the `compute_budget_unit_price` is denominated in micro Lamports. +fn get_compute_budget_priority_fee() -> Result<(u32, u64), Error> { + // Intent is to check first several instructions in hopes to find ComputeBudget ones. + let max_idx = 5; + + let mut idx = 0; + let mut compute_unit_limit: Option = None; + let mut compute_unit_price: Option = None; + while (compute_unit_limit.is_none() || compute_unit_price.is_none()) && idx < max_idx { + let ixn_option = get_processed_sibling_instruction(idx); + if ixn_option.is_none() { + // If the current instruction is empty, break from the cycle. + break; + } + + let cur_ixn = ixn_option.unwrap(); + // Skip all instructions that do not target Compute Budget Program. + if cur_ixn.program_id != COMPUTE_BUDGET_ADDRESS { + idx += 1; + continue; + } + + // As of now, data of ComputeBudgetInstruction is always non-empty. + // This is a sanity check to have a safe future-proof implementation. + let tag = cur_ixn.data.first().unwrap_or(&0); + match *tag { + COMPUTE_UNIT_LIMIT_TAG => { + compute_unit_limit = Some(u32::from_le_bytes( + cur_ixn.data[1..].try_into().map_err(|_| { + Error::PriorityFeeParsingError( + "Invalid format of compute unit limit.".to_string(), + ) + })?, + )); + } + COMPUTE_UNIT_PRICE_TAG => { + compute_unit_price = Some(u64::from_le_bytes( + cur_ixn.data[1..].try_into().map_err(|_| { + Error::PriorityFeeParsingError( + "Invalid format of compute unit price.".to_string(), + ) + })?, + )); + } + _ => (), + } + idx += 1; + } + + if compute_unit_price.is_none() { + return Err(Error::PriorityFeeNotSpecified); + } + + // Caller may not specify the compute unit limit, the default should take effect. + if compute_unit_limit.is_none() { + compute_unit_limit = Some(DEFAULT_COMPUTE_UNIT_LIMIT); + } + + // Both are not none, it's safe to unwrap. + Ok((compute_unit_limit.unwrap(), compute_unit_price.unwrap())) +} diff --git a/evm_loader/program/src/instruction/test_account_update_nonce.rs b/evm_loader/program/src/instruction/test_account_update_nonce.rs deleted file mode 100644 index f974eacf4..000000000 --- a/evm_loader/program/src/instruction/test_account_update_nonce.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::{ - account::EthereumAccount, - error::{Error, Result}, -}; -use arrayref::array_ref; -use solana_program::{ - account_info::AccountInfo, - bpf_loader_upgradeable::{self, UpgradeableLoaderState}, - program_error::ProgramError, - pubkey::Pubkey, -}; - -struct Accounts<'a> { - program_data: &'a AccountInfo<'a>, - signer: &'a AccountInfo<'a>, - ethereum_account: EthereumAccount<'a>, -} - -fn get_program_upgrade_authority( - program_id: &Pubkey, - program_data: &AccountInfo, -) -> Result { - let (expected_key, _) = - Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id()); - - if *program_data.key != expected_key { - return Err(Error::AccountInvalidKey(*program_data.key, expected_key)); - } - - match bincode::deserialize(&program_data.data.borrow())? { - UpgradeableLoaderState::ProgramData { - upgrade_authority_address, - .. - } => upgrade_authority_address - .ok_or_else(|| "NeonEVM must have valid upgrade authority".into()), - _ => Err(Error::AccountInvalidData(*program_data.key)), - } -} - -pub fn process<'a>( - program_id: &'a Pubkey, - accounts: &'a [AccountInfo<'a>], - instruction: &[u8], -) -> Result<()> { - solana_program::msg!("Instruction: TEST Set Account Nonce"); - - if cfg!(feature = "mainnet") { - return Err(ProgramError::InvalidInstructionData.into()); - } - - if !(cfg!(feature = "devnet") || cfg!(feature = "testnet") || cfg!(feature = "ci")) { - return Err(ProgramError::InvalidInstructionData.into()); - } - - let nonce = u64::from_le_bytes(*array_ref![instruction, 0, 8]); - - let mut accounts = Accounts { - signer: &accounts[0], - program_data: &accounts[1], - ethereum_account: EthereumAccount::from_account(program_id, &accounts[2])?, - }; - - if !accounts.signer.is_signer { - return Err(Error::AccountNotSigner(*accounts.signer.key)); - } - - let upgrade_authority = get_program_upgrade_authority(program_id, accounts.program_data)?; - if upgrade_authority != *accounts.signer.key { - return Err("Signer is not program upgrade authority".into()); - } - - accounts.ethereum_account.trx_count = nonce; - - Ok(()) -} diff --git a/evm_loader/program/src/instruction/transaction_cancel.rs b/evm_loader/program/src/instruction/transaction_cancel.rs index 532edbba4..86e30e046 100644 --- a/evm_loader/program/src/instruction/transaction_cancel.rs +++ b/evm_loader/program/src/instruction/transaction_cancel.rs @@ -1,90 +1,80 @@ -use crate::account::{EthereumAccount, Incinerator, Operator, State}; -use crate::state_account::{BlockedAccounts, Deposit}; +use crate::account::{AccountsDB, BalanceAccount, Operator, OperatorBalanceAccount, StateAccount}; +use crate::config::DEFAULT_CHAIN_ID; +use crate::debug::log_data; +use crate::error::{Error, Result}; +use crate::gasometer::{CANCEL_TRX_COST, LAST_ITERATION_COST}; +use crate::instruction::priority_fee_txn_calculator; use arrayref::array_ref; use ethnum::U256; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; - -struct Accounts<'a> { - storage: State<'a>, - incinerator: Incinerator<'a>, - remaining_accounts: &'a [AccountInfo<'a>], -} +use solana_program::rent::Rent; +use solana_program::sysvar::Sysvar; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; pub fn process<'a>( program_id: &'a Pubkey, accounts: &'a [AccountInfo<'a>], instruction: &[u8], -) -> ProgramResult { - solana_program::msg!("Instruction: Cancel Transaction"); +) -> Result<()> { + log_msg!("Instruction: Cancel Transaction"); - let storage_info = &accounts[0]; - let operator = Operator::from_account(&accounts[1])?; - let incinerator = Incinerator::from_account(&accounts[2])?; - let remaining_accounts = &accounts[3..]; - - let (storage, blocked_accounts) = State::restore( - program_id, - storage_info, - &operator, - remaining_accounts, - true, - )?; - - let accounts = Accounts { - storage, - incinerator, - remaining_accounts, - }; let transaction_hash = array_ref![instruction, 0, 32]; - solana_program::log::sol_log_data(&[b"HASH", transaction_hash]); + let storage_info = accounts[0].clone(); + let operator = Operator::from_account(&accounts[1])?; + let operator_balance = OperatorBalanceAccount::from_account(program_id, &accounts[2])?; - validate(&accounts, transaction_hash)?; - execute(program_id, accounts, &blocked_accounts) -} + operator_balance.validate_owner(&operator)?; -fn validate(accounts: &Accounts, transaction_hash: &[u8; 32]) -> ProgramResult { - let storage = &accounts.storage; + log_data(&[b"HASH", transaction_hash]); + log_data(&[b"MINER", operator_balance.address().as_bytes()]); + + let accounts_db = AccountsDB::new(&accounts[3..], operator, Some(operator_balance), None, None); + let (storage, _) = StateAccount::restore(program_id, &storage_info, &accounts_db)?; + + validate(&storage, transaction_hash)?; + execute(program_id, accounts_db, storage) +} - if &storage.transaction_hash != transaction_hash { - return Err!(ProgramError::InvalidInstructionData; "Invalid transaction hash"); +fn validate(storage: &StateAccount, transaction_hash: &[u8; 32]) -> Result<()> { + if &storage.trx().hash() != transaction_hash { + return Err(Error::HolderInvalidHash( + storage.trx().hash(), + *transaction_hash, + )); } Ok(()) } fn execute<'a>( - program_id: &'a Pubkey, - accounts: Accounts<'a>, - blocked_accounts: &BlockedAccounts, -) -> ProgramResult { + program_id: &Pubkey, + accounts: AccountsDB<'a>, + mut storage: StateAccount<'a>, +) -> Result<()> { + let trx_chain_id = storage.trx().chain_id().unwrap_or(DEFAULT_CHAIN_ID); + let used_gas = U256::ZERO; - let total_used_gas = accounts.storage.gas_used; - solana_program::log::sol_log_data(&[ + let total_used_gas = storage.gas_used(); + log_data(&[ b"GAS", &used_gas.to_le_bytes(), &total_used_gas.to_le_bytes(), ]); - for (info, blocked) in accounts.remaining_accounts.iter().zip(blocked_accounts) { - if !blocked.exists { - continue; - } - - if let Ok(mut ether_account) = EthereumAccount::from_account(program_id, info) { - ether_account.rw_blocked = false; - if ether_account.address == accounts.storage.caller { - ether_account.trx_count += 1; - } - } - } + let gas = U256::from(CANCEL_TRX_COST + LAST_ITERATION_COST); + let priority_fee = priority_fee_txn_calculator::handle_priority_fee(storage.trx(), gas)?; + let _ = storage.consume_gas(gas, priority_fee, accounts.try_operator_balance()); // ignore error - accounts - .storage - .finalize(Deposit::Burn(accounts.incinerator))?; + let origin = storage.trx_origin(); + let (origin_pubkey, _) = origin.find_balance_address(program_id, trx_chain_id); - Ok(()) + { + let origin_info = accounts.get(&origin_pubkey).clone(); + let mut account = BalanceAccount::from_account(program_id, origin_info)?; + account.increment_revision(&Rent::get()?, &accounts)?; + + storage.refund_unused_gas(&mut account)?; + } + + storage.finalize(program_id) } diff --git a/evm_loader/program/src/instruction/transaction_execute.rs b/evm_loader/program/src/instruction/transaction_execute.rs index 46a7d0ca5..ef5330ce4 100644 --- a/evm_loader/program/src/instruction/transaction_execute.rs +++ b/evm_loader/program/src/instruction/transaction_execute.rs @@ -1,96 +1,119 @@ -use crate::account::{program, EthereumAccount, Operator, Treasury}; -use crate::account_storage::{AccountsReadiness, ProgramAccountStorage}; -use crate::config::CHAIN_ID; +use crate::account::{AccountsDB, AllocateResult}; +use crate::account_storage::ProgramAccountStorage; +use crate::debug::log_data; use crate::error::{Error, Result}; +use crate::evm::tracing::NoopEventListener; use crate::evm::Machine; -use crate::executor::ExecutorState; +use crate::executor::{ExecutorState, ExecutorStateData, SyncedExecutorState}; use crate::gasometer::Gasometer; +use crate::instruction::priority_fee_txn_calculator; use crate::instruction::transaction_step::log_return_value; -use crate::types::{Address, Transaction}; -use ethnum::U256; -use solana_program::account_info::AccountInfo; - -pub struct Accounts<'a> { - pub operator: Operator<'a>, - pub treasury: Treasury<'a>, - pub operator_ether_account: EthereumAccount<'a>, - pub system_program: program::System<'a>, - pub neon_program: program::Neon<'a>, - pub remaining_accounts: &'a [AccountInfo<'a>], - pub all_accounts: &'a [AccountInfo<'a>], -} +use crate::types::{boxx::Boxx, Address, Transaction}; -pub fn validate( - _accounts: &Accounts, - account_storage: &ProgramAccountStorage, - trx: &Transaction, - _caller_address: &Address, +pub fn execute( + accounts: AccountsDB<'_>, + mut gasometer: Gasometer, + trx: Boxx, + origin: Address, ) -> Result<()> { - if trx.chain_id != Some(CHAIN_ID.into()) { - return Err(Error::InvalidChainId(trx.chain_id.unwrap_or(U256::ZERO))); + let chain_id = trx.chain_id().unwrap_or(crate::config::DEFAULT_CHAIN_ID); + let gas_limit = trx.gas_limit(); + let gas_price = trx.gas_price(); + + let mut account_storage = ProgramAccountStorage::new(accounts)?; + let mut backend_data = ExecutorStateData::new(&account_storage); + + trx.validate(origin, &account_storage)?; + + account_storage.origin(origin, &trx)?.increment_nonce()?; + + let (exit_reason, steps_executed) = { + let mut backend = ExecutorState::new(&mut account_storage, &mut backend_data); + + let mut evm = Machine::new(&trx, origin, &mut backend, None::)?; + let (result, steps_executed, _) = evm.execute(u64::MAX, &mut backend)?; + + (result, steps_executed) + }; + + let apply_state = backend_data.into_actions(); + + log_data(&[ + b"STEPS", + &steps_executed.to_le_bytes(), // Iteration steps + &steps_executed.to_le_bytes(), // Total steps is the same as iteration steps + ]); + + let allocate_result = account_storage.allocate(apply_state)?; + if allocate_result != AllocateResult::Ready { + return Err(Error::AccountSpaceAllocationFailure); } - account_storage.check_for_blocked_accounts()?; + account_storage.apply_state_change(apply_state)?; + account_storage.transfer_treasury_payment()?; + + gasometer.record_operator_expenses(account_storage.operator()); + let used_gas = gasometer.used_gas(); + if used_gas > gas_limit { + return Err(Error::OutOfGas(gas_limit, used_gas)); + } + + log_data(&[b"GAS", &used_gas.to_le_bytes(), &used_gas.to_le_bytes()]); + + let gas_cost = used_gas.saturating_mul(gas_price); + let priority_fee = priority_fee_txn_calculator::handle_priority_fee(&trx, used_gas)?; + account_storage.transfer_gas_payment(origin, chain_id, gas_cost + priority_fee)?; + + log_return_value(&exit_reason); Ok(()) } -pub fn execute<'a>( - accounts: Accounts<'a>, - account_storage: &mut ProgramAccountStorage<'a>, +pub fn execute_with_solana_call( + accounts: AccountsDB<'_>, mut gasometer: Gasometer, - trx: Transaction, - caller_address: Address, + trx: Boxx, + origin: Address, ) -> Result<()> { - accounts.system_program.transfer( - &accounts.operator, - &accounts.treasury, - crate::config::PAYMENT_TO_TREASURE, - )?; + let chain_id = trx.chain_id().unwrap_or(crate::config::DEFAULT_CHAIN_ID); + let gas_limit = trx.gas_limit(); + let gas_price = trx.gas_price(); - let gas_limit = trx.gas_limit; - let gas_price = trx.gas_price; + let mut account_storage = ProgramAccountStorage::new(accounts)?; - let (exit_reason, apply_state) = { - let mut backend = ExecutorState::new(account_storage); + trx.validate(origin, &account_storage)?; - let mut evm = Machine::new(trx, caller_address, &mut backend)?; - let (result, _) = evm.execute(u64::MAX, &mut backend)?; + account_storage.origin(origin, &trx)?.increment_nonce()?; - let actions = backend.into_actions(); + let (exit_reason, steps_executed) = { + let mut backend = SyncedExecutorState::new(&mut account_storage); - (result, actions) + let mut evm = Machine::new(&trx, origin, &mut backend, None::)?; + let (result, steps_executed, _) = evm.execute(u64::MAX, &mut backend)?; + + (result, steps_executed) }; - let accounts_readiness = account_storage.apply_state_change( - &accounts.neon_program, - &accounts.system_program, - &accounts.operator, - apply_state, - )?; - - assert_eq!( - accounts_readiness, - AccountsReadiness::Ready, - "Deployment of contract which needs more than 10kb of account space needs several \ - transactions for reallocation and cannot be performed in a single instruction. \ - That's why you have to use iterative transaction for the deployment.", - ); - - gasometer.record_operator_expenses(&accounts.operator); + log_data(&[ + b"STEPS", + &steps_executed.to_le_bytes(), // Iteration steps + &steps_executed.to_le_bytes(), // Total steps is the same as iteration steps + ]); + + account_storage.increment_revision_for_modified_contracts()?; + account_storage.transfer_treasury_payment()?; + + gasometer.record_operator_expenses(account_storage.operator()); let used_gas = gasometer.used_gas(); if used_gas > gas_limit { return Err(Error::OutOfGas(gas_limit, used_gas)); } - solana_program::log::sol_log_data(&[b"GAS", &used_gas.to_le_bytes(), &used_gas.to_le_bytes()]); + log_data(&[b"GAS", &used_gas.to_le_bytes(), &used_gas.to_le_bytes()]); let gas_cost = used_gas.saturating_mul(gas_price); - account_storage.transfer_gas_payment( - caller_address, - accounts.operator_ether_account, - gas_cost, - )?; + let priority_fee = priority_fee_txn_calculator::handle_priority_fee(&trx, used_gas)?; + account_storage.transfer_gas_payment(origin, chain_id, gas_cost + priority_fee)?; log_return_value(&exit_reason); diff --git a/evm_loader/program/src/instruction/transaction_execute_from_account.rs b/evm_loader/program/src/instruction/transaction_execute_from_account.rs index c7771ede8..fe1256a18 100644 --- a/evm_loader/program/src/instruction/transaction_execute_from_account.rs +++ b/evm_loader/program/src/instruction/transaction_execute_from_account.rs @@ -1,10 +1,13 @@ -use crate::account::{program, EthereumAccount, Holder, Operator, Treasury}; -use crate::account_storage::ProgramAccountStorage; +use crate::account::{ + program, AccountsDB, Holder, Operator, OperatorBalanceAccount, OperatorBalanceValidator, + Treasury, +}; +use crate::debug::log_data; use crate::error::Result; use crate::gasometer::Gasometer; -use crate::instruction::transaction_execute::Accounts; -use crate::types::Transaction; +use crate::types::{boxx::boxx, Transaction}; use arrayref::array_ref; +use ethnum::U256; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; /// Execute Ethereum transaction in a single Solana transaction @@ -13,48 +16,54 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Execute Transaction from Account"); + log_msg!("Instruction: Execute Transaction from Account"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); - let holder = Holder::from_account(program_id, &accounts[0])?; + let mut holder = Holder::from_account(program_id, accounts[0].clone())?; - let accounts = Accounts { - operator: unsafe { Operator::from_account_not_whitelisted(&accounts[1])? }, - treasury: Treasury::from_account(program_id, treasury_index, &accounts[2])?, - operator_ether_account: EthereumAccount::from_account(program_id, &accounts[3])?, - system_program: program::System::from_account(&accounts[4])?, - neon_program: program::Neon::from_account(program_id, &accounts[5])?, - remaining_accounts: &accounts[6..], - all_accounts: accounts, - }; + let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1])? }; + let treasury = Treasury::from_account(program_id, treasury_index, &accounts[2])?; + let operator_balance = OperatorBalanceAccount::try_from_account(program_id, &accounts[3])?; + let system = program::System::from_account(&accounts[4])?; - holder.validate_owner(&accounts.operator)?; - let trx = Transaction::from_rlp(&holder.transaction())?; + holder.validate_owner(&operator)?; + + // We have to initialize the heap before creating Transaction object, but since + // transaction's rlp itself is stored in the holder account, we have two options: + // 1. Copy the rlp and initialize the heap right after the holder's header. + // This way, the space occupied by the rlp within holder will be reused. + // 2. Don't copy the rlp, initialize the heap after transaction rlp in the holder. + // The first option (chosen) saves the holder space in exchange for compute units. + // The second option wastes the holder space (because transaction bytes will be + // stored two times), but doesnt copy. + let transaction_rlp_copy = holder.transaction().to_vec(); + holder.init_heap(0)?; + + let trx = boxx(Transaction::from_rlp(&transaction_rlp_copy)?); holder.validate_transaction(&trx)?; - let caller_address = trx.recover_caller_address()?; + let origin = trx.recover_caller_address()?; + + operator_balance.validate_owner(&operator)?; + operator_balance.validate_transaction(&trx)?; + let miner_address = operator_balance.miner(origin); - solana_program::log::sol_log_data(&[b"HASH", &trx.hash]); + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); - let mut account_storage = ProgramAccountStorage::new( - program_id, - &accounts.operator, - Some(&accounts.system_program), - accounts.remaining_accounts, - )?; + let accounts_db = AccountsDB::new( + &accounts[5..], + operator, + operator_balance, + Some(system), + Some(treasury), + ); - let mut gasometer = Gasometer::new(None, &accounts.operator)?; + let mut gasometer = Gasometer::new(U256::ZERO, accounts_db.operator())?; gasometer.record_solana_transaction_cost(); - gasometer.record_address_lookup_table(accounts.all_accounts); + gasometer.record_address_lookup_table(accounts); gasometer.record_write_to_holder(&trx); - super::transaction_execute::validate(&accounts, &account_storage, &trx, &caller_address)?; - super::transaction_execute::execute( - accounts, - &mut account_storage, - gasometer, - trx, - caller_address, - ) + super::transaction_execute::execute(accounts_db, gasometer, trx, origin) } diff --git a/evm_loader/program/src/instruction/transaction_execute_from_account_solana_call.rs b/evm_loader/program/src/instruction/transaction_execute_from_account_solana_call.rs new file mode 100644 index 000000000..a78e51721 --- /dev/null +++ b/evm_loader/program/src/instruction/transaction_execute_from_account_solana_call.rs @@ -0,0 +1,73 @@ +use crate::account::{ + program, AccountsDB, Holder, Operator, OperatorBalanceAccount, OperatorBalanceValidator, + Treasury, +}; +use crate::debug::log_data; +use crate::error::Result; +use crate::gasometer::Gasometer; +use crate::types::{boxx::boxx, Transaction}; +use arrayref::array_ref; +use ethnum::U256; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +/// Execute Ethereum transaction in a single Solana transaction +pub fn process<'a>( + program_id: &'a Pubkey, + accounts: &'a [AccountInfo<'a>], + instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Execute Transaction from Account with Solana call"); + + let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); + + let mut holder = Holder::from_account(program_id, accounts[0].clone())?; + + let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1])? }; + let treasury = Treasury::from_account(program_id, treasury_index, &accounts[2])?; + let operator_balance = OperatorBalanceAccount::try_from_account(program_id, &accounts[3])?; + let system = program::System::from_account(&accounts[4])?; + + holder.validate_owner(&operator)?; + + // We have to initialize the heap before creating Transaction object, but since + // transaction's rlp itself is stored in the holder account, we have two options: + // 1. Copy the rlp and initialize the heap right after the holder's header. + // This way, the space occupied by the rlp within holder will be reused. + // 2. Don't copy the rlp, initialize the heap after transaction rlp in the holder. + // The first option (chosen) saves the holder space in exchange for compute units. + // The second option wastes the holder space (because transaction bytes will be + // stored two times), but doesnt copy. + let transaction_rlp_copy = { + let holder_transaction_ref = holder.transaction(); + let mut transaction_copy = vec![0u8; holder_transaction_ref.len()]; + transaction_copy.copy_from_slice(&holder_transaction_ref); + transaction_copy + }; + holder.init_heap(0)?; + + let trx = boxx(Transaction::from_rlp(&transaction_rlp_copy)?); + holder.validate_transaction(&trx)?; + + let origin = trx.recover_caller_address()?; + operator_balance.validate_owner(&operator)?; + operator_balance.validate_transaction(&trx)?; + let miner_address = operator_balance.miner(origin); + + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); + + let accounts_db = AccountsDB::new( + &accounts[5..], + operator, + operator_balance, + Some(system), + Some(treasury), + ); + + let mut gasometer = Gasometer::new(U256::ZERO, accounts_db.operator())?; + gasometer.record_solana_transaction_cost(); + gasometer.record_address_lookup_table(accounts); + gasometer.record_write_to_holder(&trx); + + super::transaction_execute::execute_with_solana_call(accounts_db, gasometer, trx, origin) +} diff --git a/evm_loader/program/src/instruction/transaction_execute_from_instruction.rs b/evm_loader/program/src/instruction/transaction_execute_from_instruction.rs index 528e006ea..14c97d50c 100644 --- a/evm_loader/program/src/instruction/transaction_execute_from_instruction.rs +++ b/evm_loader/program/src/instruction/transaction_execute_from_instruction.rs @@ -1,10 +1,13 @@ -use crate::account::{program, EthereumAccount, Operator, Treasury}; -use crate::account_storage::ProgramAccountStorage; +use crate::account::{ + program, AccountsDB, Holder, Operator, OperatorBalanceAccount, OperatorBalanceValidator, + Treasury, +}; +use crate::debug::log_data; use crate::error::Result; use crate::gasometer::Gasometer; -use crate::instruction::transaction_execute::Accounts; -use crate::types::Transaction; +use crate::types::{boxx::boxx, Transaction}; use arrayref::array_ref; +use ethnum::U256; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; /// Execute Ethereum transaction in a single Solana transaction @@ -13,43 +16,41 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Execute Transaction from Instruction"); + log_msg!("Instruction: Execute Transaction from Instruction"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); let messsage = &instruction[4..]; - let accounts = Accounts { - operator: unsafe { Operator::from_account_not_whitelisted(&accounts[0])? }, - treasury: Treasury::from_account(program_id, treasury_index, &accounts[1])?, - operator_ether_account: EthereumAccount::from_account(program_id, &accounts[2])?, - system_program: program::System::from_account(&accounts[3])?, - neon_program: program::Neon::from_account(program_id, &accounts[4])?, - remaining_accounts: &accounts[5..], - all_accounts: accounts, - }; - - let trx = Transaction::from_rlp(messsage)?; - let caller_address = trx.recover_caller_address()?; - - solana_program::log::sol_log_data(&[b"HASH", &trx.hash]); - - let mut account_storage = ProgramAccountStorage::new( - program_id, - &accounts.operator, - Some(&accounts.system_program), - accounts.remaining_accounts, - )?; - - let mut gasometer = Gasometer::new(None, &accounts.operator)?; + let mut holder = Holder::from_account(program_id, accounts[0].clone())?; + let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1])? }; + let treasury = Treasury::from_account(program_id, treasury_index, &accounts[2])?; + let operator_balance = OperatorBalanceAccount::try_from_account(program_id, &accounts[3])?; + let system = program::System::from_account(&accounts[4])?; + + holder.validate_owner(&operator)?; + holder.init_heap(0)?; + + let trx = boxx(Transaction::from_rlp(messsage)?); + let origin = trx.recover_caller_address()?; + + operator_balance.validate_owner(&operator)?; + operator_balance.validate_transaction(&trx)?; + let miner_address = operator_balance.miner(origin); + + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); + + let accounts_db = AccountsDB::new( + &accounts[5..], + operator, + operator_balance, + Some(system), + Some(treasury), + ); + + let mut gasometer = Gasometer::new(U256::ZERO, accounts_db.operator())?; gasometer.record_solana_transaction_cost(); - gasometer.record_address_lookup_table(accounts.all_accounts); - - super::transaction_execute::validate(&accounts, &account_storage, &trx, &caller_address)?; - super::transaction_execute::execute( - accounts, - &mut account_storage, - gasometer, - trx, - caller_address, - ) + gasometer.record_address_lookup_table(accounts); + + super::transaction_execute::execute(accounts_db, gasometer, trx, origin) } diff --git a/evm_loader/program/src/instruction/transaction_execute_from_instruction_solana_call.rs b/evm_loader/program/src/instruction/transaction_execute_from_instruction_solana_call.rs new file mode 100644 index 000000000..63e259bc8 --- /dev/null +++ b/evm_loader/program/src/instruction/transaction_execute_from_instruction_solana_call.rs @@ -0,0 +1,56 @@ +use crate::account::{ + program, AccountsDB, Holder, Operator, OperatorBalanceAccount, OperatorBalanceValidator, + Treasury, +}; +use crate::debug::log_data; +use crate::error::Result; +use crate::gasometer::Gasometer; +use crate::types::{boxx::boxx, Transaction}; +use arrayref::array_ref; +use ethnum::U256; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +/// Execute Ethereum transaction in a single Solana transaction +pub fn process<'a>( + program_id: &'a Pubkey, + accounts: &'a [AccountInfo<'a>], + instruction: &[u8], +) -> Result<()> { + log_msg!("Instruction: Execute Transaction from Instruction with Solana call"); + + let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); + let messsage = &instruction[4..]; + + let mut holder = Holder::from_account(program_id, accounts[0].clone())?; + let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1])? }; + let treasury = Treasury::from_account(program_id, treasury_index, &accounts[2])?; + let operator_balance = OperatorBalanceAccount::try_from_account(program_id, &accounts[3])?; + let system = program::System::from_account(&accounts[4])?; + + holder.validate_owner(&operator)?; + holder.init_heap(0)?; + + let trx = boxx(Transaction::from_rlp(messsage)?); + let origin = trx.recover_caller_address()?; + + operator_balance.validate_owner(&operator)?; + operator_balance.validate_transaction(&trx)?; + let miner_address = operator_balance.miner(origin); + + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); + + let accounts_db = AccountsDB::new( + &accounts[5..], + operator, + operator_balance, + Some(system), + Some(treasury), + ); + + let mut gasometer = Gasometer::new(U256::ZERO, accounts_db.operator())?; + gasometer.record_solana_transaction_cost(); + gasometer.record_address_lookup_table(accounts); + + super::transaction_execute::execute_with_solana_call(accounts_db, gasometer, trx, origin) +} diff --git a/evm_loader/program/src/instruction/transaction_step.rs b/evm_loader/program/src/instruction/transaction_step.rs index 628183824..8a62b6b1f 100644 --- a/evm_loader/program/src/instruction/transaction_step.rs +++ b/evm_loader/program/src/instruction/transaction_step.rs @@ -1,142 +1,176 @@ -use ethnum::U256; -use solana_program::account_info::AccountInfo; +use solana_program::pubkey::Pubkey; -use crate::account::{program, EthereumAccount, Operator, State, Treasury}; -use crate::account_storage::{AccountsReadiness, ProgramAccountStorage}; -use crate::config::{EVM_STEPS_LAST_ITERATION_MAX, EVM_STEPS_MIN, PAYMENT_TO_TREASURE}; +use crate::account::{AccountsDB, AllocateResult, StateAccount}; +use crate::account_storage::{AccountStorage, ProgramAccountStorage}; +use crate::config::{EVM_STEPS_LAST_ITERATION_MAX, EVM_STEPS_MIN}; +use crate::debug::log_data; use crate::error::{Error, Result}; +use crate::evm::tracing::NoopEventListener; use crate::evm::{ExitStatus, Machine}; -use crate::executor::{Action, ExecutorState}; -use crate::gasometer::Gasometer; -use crate::state_account::Deposit; -use crate::types::{Address, Transaction}; +use crate::executor::{Action, ExecutorState, ExecutorStateData}; +use crate::gasometer::{Gasometer, LAMPORTS_PER_SIGNATURE}; +use crate::instruction::priority_fee_txn_calculator; +use crate::types::boxx::boxx; +use crate::types::TreeMap; +use crate::types::Vector; type EvmBackend<'a, 'r> = ExecutorState<'r, ProgramAccountStorage<'a>>; -type Evm<'a, 'r> = Machine>; - -pub struct Accounts<'a> { - pub operator: Operator<'a>, - pub treasury: Treasury<'a>, - pub operator_ether_account: EthereumAccount<'a>, - pub system_program: program::System<'a>, - pub neon_program: program::Neon<'a>, - pub remaining_accounts: &'a [AccountInfo<'a>], - pub all_accounts: &'a [AccountInfo<'a>], -} +type Evm<'a, 'r> = Machine, NoopEventListener>; pub fn do_begin<'a>( - accounts: Accounts<'a>, - mut storage: State<'a>, - account_storage: &mut ProgramAccountStorage<'a>, + accounts: AccountsDB<'a>, + mut storage: StateAccount<'a>, gasometer: Gasometer, - trx: Transaction, - caller: Address, ) -> Result<()> { debug_print!("do_begin"); - account_storage.check_for_blocked_accounts()?; - account_storage.block_accounts(true); + let mut account_storage = ProgramAccountStorage::new(accounts)?; + + let origin = storage.trx_origin(); + + storage.trx().validate(origin, &account_storage)?; - let mut backend = ExecutorState::new(account_storage); - let evm = Machine::new(trx, caller, &mut backend)?; + // Increment origin nonce in the first iteration + // This allows us to run multiple iterative transactions from the same sender in parallel + // These transactions are guaranteed to start in a correct sequence + // BUT they finalize in an undefined order + let mut origin_account = account_storage.origin(origin, storage.trx())?; + origin_account.increment_revision(account_storage.rent(), account_storage.db())?; + origin_account.increment_nonce()?; - serialize_evm_state(&mut storage, &backend, &evm)?; + // Burn `gas_limit` tokens (both base fee and priority, if any) from the origin account. + // Later we will mint them to the operator. + // Remaining tokens are returned to the origin in the last iteration. + let gas_limit_in_tokens = storage.trx().gas_limit_in_tokens()?; + let max_priority_fee_in_tokens = storage.trx().priority_fee_limit_in_tokens()?; + origin_account.burn(gas_limit_in_tokens + max_priority_fee_in_tokens)?; - finalize(0, accounts, storage, account_storage, None, gasometer) + allocate_or_reinit_state(&mut account_storage, &mut storage, true)?; + let mut state_data = storage.read_executor_state(); + + let (_, touched_accounts) = state_data.deconstruct(); + finalize( + 0, + storage, + account_storage, + None, + gasometer, + touched_accounts, + ) } pub fn do_continue<'a>( step_count: u64, - accounts: Accounts<'a>, - mut storage: State<'a>, - account_storage: &mut ProgramAccountStorage<'a>, + accounts: AccountsDB<'a>, + mut storage: StateAccount<'a>, gasometer: Gasometer, + reset: bool, ) -> Result<()> { debug_print!("do_continue"); - if (step_count < EVM_STEPS_MIN) && (storage.gas_price > 0) { + if (step_count < EVM_STEPS_MIN) && (storage.trx().gas_price() > 0) { return Err(Error::Custom(format!( "Step limit {step_count} below minimum {EVM_STEPS_MIN}" ))); } - let (mut backend, mut evm) = deserialize_evm_state(&storage, account_storage)?; + let mut account_storage = ProgramAccountStorage::new(accounts)?; + if reset { + log_data(&[b"RESET"]); + } + + allocate_or_reinit_state(&mut account_storage, &mut storage, reset)?; + let mut state_data = storage.read_executor_state(); + let mut evm = storage.read_evm::(); + let mut backend = ExecutorState::new(&mut account_storage, &mut state_data); - let (result, steps_executed) = { - match backend.exit_status() { - Some(status) => (status.clone(), 0_u64), - None => evm.execute(step_count, &mut backend)?, + let mut steps_executed = 0; + if backend.exit_status().is_none() { + let (exit_status, steps_returned, _) = evm.execute(step_count, &mut backend)?; + if exit_status != ExitStatus::StepLimit { + backend.set_exit_status(exit_status) } - }; - if (result != ExitStatus::StepLimit) && (steps_executed > 0) { - backend.set_exit_status(result.clone()); + steps_executed = steps_returned; } - if steps_executed > 0 { - serialize_evm_state(&mut storage, &backend, &evm)?; + let (mut results, touched_accounts) = state_data.deconstruct(); + if steps_executed > EVM_STEPS_LAST_ITERATION_MAX { + results = None; } - let results = match result { - ExitStatus::StepLimit => None, - _ if steps_executed > EVM_STEPS_LAST_ITERATION_MAX => None, - result => Some((result, backend.into_actions())), - }; - finalize( steps_executed, - accounts, storage, account_storage, results, gasometer, + touched_accounts, ) } -fn pay_gas_cost<'a>( - used_gas: U256, - operator_ether_account: EthereumAccount<'a>, - storage: &mut State<'a>, - account_storage: &mut ProgramAccountStorage<'a>, +fn allocate_or_reinit_state( + account_storage: &mut ProgramAccountStorage<'_>, + storage: &mut StateAccount<'_>, + is_allocate: bool, ) -> Result<()> { - debug_print!("pay_gas_cost {}", used_gas); + if is_allocate { + storage.reset_steps_executed(); - // Can overflow in malicious transaction - let value = used_gas.saturating_mul(storage.gas_price); - storage.gas_used = storage.gas_used.saturating_add(used_gas); + // Dealloc objects that were potentially alloced in previous iterations before the reset. + if storage.is_evm_alloced() { + storage.dealloc_evm::(); + } + if storage.is_executor_state_alloced() { + storage.dealloc_executor_state(); + } - account_storage.transfer_gas_payment(storage.caller, operator_ether_account, value)?; + let mut state_data = boxx(ExecutorStateData::new(account_storage)); + let mut evm_backend = ExecutorState::new(account_storage, &mut state_data); + let evm = boxx(Evm::new( + storage.trx(), + storage.trx_origin(), + &mut evm_backend, + None, + )?); + storage.alloc_evm(evm); + storage.alloc_executor_state(state_data); + } else { + let mut state_data = storage.read_executor_state(); + let mut evm = storage.read_evm(); + let evm_backend = ExecutorState::new(account_storage, &mut state_data); + evm.reinit(&evm_backend); + }; Ok(()) } -fn finalize<'a>( +fn finalize<'a, 'b>( steps_executed: u64, - accounts: Accounts<'a>, - mut storage: State<'a>, - account_storage: &mut ProgramAccountStorage<'a>, - results: Option<(ExitStatus, Vec)>, + mut storage: StateAccount<'a>, + mut accounts: ProgramAccountStorage<'a>, + results: Option<(&'b ExitStatus, &'b Vector)>, mut gasometer: Gasometer, + touched_accounts: TreeMap, ) -> Result<()> { debug_print!("finalize"); + storage.update_touched_accounts(&touched_accounts)?; + storage.increment_steps_executed(steps_executed)?; + log_data(&[ + b"STEPS", + &steps_executed.to_le_bytes(), + &storage.steps_executed().to_le_bytes(), + ]); + if steps_executed > 0 { - accounts.system_program.transfer( - &accounts.operator, - &accounts.treasury, - PAYMENT_TO_TREASURE, - )?; + accounts.transfer_treasury_payment()?; } - let exit_reason_opt = if let Some((exit_reason, apply_state)) = results { - if account_storage.apply_state_change( - &accounts.neon_program, - &accounts.system_program, - &accounts.operator, - apply_state, - )? == AccountsReadiness::Ready - { - Some(exit_reason) + let status = if let Some((status, actions)) = results { + if accounts.allocate(actions)? == AllocateResult::Ready { + accounts.apply_state_change(actions)?; + Some(status) } else { None } @@ -144,41 +178,42 @@ fn finalize<'a>( None }; - gasometer.record_operator_expenses(&accounts.operator); - - let total_used_gas = gasometer.used_gas_total(); - let gas_limit = storage.gas_limit; - if total_used_gas > gas_limit { - return Err(Error::OutOfGas(gas_limit, total_used_gas)); - } + gasometer.record_operator_expenses(accounts.operator()); let used_gas = gasometer.used_gas(); - solana_program::log::sol_log_data(&[ + let total_used_gas = gasometer.used_gas_total(); + log_data(&[ b"GAS", &used_gas.to_le_bytes(), &total_used_gas.to_le_bytes(), ]); - pay_gas_cost( + // Calculate priority fee for the current iteration. + let priority_fee_in_tokens = priority_fee_txn_calculator::handle_priority_fee( + storage.trx(), + LAMPORTS_PER_SIGNATURE.into(), + )?; + + storage.consume_gas( used_gas, - accounts.operator_ether_account, - &mut storage, - account_storage, + priority_fee_in_tokens, + accounts.db().try_operator_balance(), )?; - if let Some(exit_reason) = exit_reason_opt { - log_return_value(&exit_reason); + if let Some(status) = status { + log_return_value(&status); - account_storage.block_accounts(false); - storage.finalize(Deposit::ReturnToOperator(accounts.operator))?; + let mut origin = accounts.origin(storage.trx_origin(), storage.trx())?; + origin.increment_revision(accounts.rent(), accounts.db())?; + + storage.refund_unused_gas(&mut origin)?; + storage.finalize(accounts.program_id())?; } Ok(()) } pub fn log_return_value(status: &ExitStatus) { - use solana_program::log::sol_log_data; - let code: u8 = match status { ExitStatus::Stop => 0x11, ExitStatus::Return(_) => 0x12, @@ -187,42 +222,10 @@ pub fn log_return_value(status: &ExitStatus) { ExitStatus::StepLimit => unreachable!(), }; - solana_program::msg!("exit_status={:#04X}", code); // Tests compatibility + log_msg!("exit_status={:#04X}", code); // Tests compatibility if let ExitStatus::Revert(msg) = status { crate::error::print_revert_message(msg); } - sol_log_data(&[b"RETURN", &[code]]); -} - -fn serialize_evm_state(state: &mut State, backend: &EvmBackend, machine: &Evm) -> Result<()> { - let (evm_state_len, evm_machine_len) = { - let mut buffer = state.evm_data_mut(); - let backend_bytes = backend.serialize_into(&mut buffer)?; - - let buffer = &mut buffer[backend_bytes..]; - let evm_bytes = machine.serialize_into(buffer)?; - - (backend_bytes, evm_bytes) - }; - - state.evm_state_len = evm_state_len; - state.evm_machine_len = evm_machine_len; - - Ok(()) -} - -fn deserialize_evm_state<'a, 'r>( - state: &State<'a>, - account_storage: &'r ProgramAccountStorage<'a>, -) -> Result<(EvmBackend<'a, 'r>, Evm<'a, 'r>)> { - let buffer = state.evm_data(); - - let executor_state_data = &buffer[..state.evm_state_len]; - let backend = ExecutorState::deserialize_from(executor_state_data, account_storage)?; - - let evm_data = &buffer[state.evm_state_len..][..state.evm_machine_len]; - let evm = Machine::deserialize_from(evm_data, &backend)?; - - Ok((backend, evm)) + log_data(&[b"RETURN", &[code]]); } diff --git a/evm_loader/program/src/instruction/transaction_step_from_account.rs b/evm_loader/program/src/instruction/transaction_step_from_account.rs index ee3f06f7c..01207fed8 100644 --- a/evm_loader/program/src/instruction/transaction_step_from_account.rs +++ b/evm_loader/program/src/instruction/transaction_step_from_account.rs @@ -1,9 +1,12 @@ -use crate::account::{program, EthereumAccount, FinalizedState, Holder, Operator, State, Treasury}; -use crate::account_storage::ProgramAccountStorage; -use crate::config::{CHAIN_ID, GAS_LIMIT_MULTIPLIER_NO_CHAINID}; +use crate::account::legacy::{TAG_HOLDER_DEPRECATED, TAG_STATE_FINALIZED_DEPRECATED}; +use crate::account::{ + program, AccountsDB, AccountsStatus, Holder, Operator, OperatorBalanceAccount, + OperatorBalanceValidator, StateAccount, Treasury, TAG_HOLDER, TAG_STATE, TAG_STATE_FINALIZED, +}; +use crate::debug::log_data; use crate::error::{Error, Result}; use crate::gasometer::Gasometer; -use crate::instruction::transaction_step::{do_begin, do_continue, Accounts}; +use crate::instruction::transaction_step::{do_begin, do_continue}; use crate::types::Transaction; use arrayref::array_ref; use ethnum::U256; @@ -14,105 +17,114 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Begin or Continue Transaction from Account"); + log_msg!("Instruction: Begin or Continue Transaction from Account"); - let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); - let step_count = u64::from(u32::from_le_bytes(*array_ref![instruction, 4, 4])); - - let holder_or_storage_info = &accounts[0]; - - let accounts = Accounts { - operator: Operator::from_account(&accounts[1])?, - treasury: Treasury::from_account(program_id, treasury_index, &accounts[2])?, - operator_ether_account: EthereumAccount::from_account(program_id, &accounts[3])?, - system_program: program::System::from_account(&accounts[4])?, - neon_program: program::Neon::from_account(program_id, &accounts[5])?, - remaining_accounts: &accounts[6..], - all_accounts: accounts, - }; - - let mut account_storage = ProgramAccountStorage::new( - program_id, - &accounts.operator, - Some(&accounts.system_program), - accounts.remaining_accounts, - )?; - - execute( - program_id, - holder_or_storage_info, - accounts, - &mut account_storage, - step_count, - Some(CHAIN_ID.into()), - ) + process_inner(program_id, accounts, instruction, false) } -pub fn execute<'a>( +pub fn process_inner<'a>( program_id: &'a Pubkey, - holder_or_storage_info: &'a AccountInfo<'a>, - accounts: Accounts<'a>, - account_storage: &mut ProgramAccountStorage<'a>, - step_count: u64, - expected_chain_id: Option, + accounts: &'a [AccountInfo<'a>], + instruction: &[u8], + increase_gas_limit: bool, ) -> Result<()> { - match crate::account::tag(program_id, holder_or_storage_info)? { - Holder::TAG => { - let trx = { - let holder = Holder::from_account(program_id, holder_or_storage_info)?; - holder.validate_owner(&accounts.operator)?; + let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); + let step_count = u64::from(u32::from_le_bytes(*array_ref![instruction, 4, 4])); + + let holder_or_storage = accounts[0].clone(); + + let operator = Operator::from_account(&accounts[1])?; + let treasury = Treasury::from_account(program_id, treasury_index, &accounts[2])?; + let operator_balance = OperatorBalanceAccount::try_from_account(program_id, &accounts[3])?; + let system = program::System::from_account(&accounts[4])?; + + operator_balance.validate_owner(&operator)?; + + let accounts_db = AccountsDB::new( + &accounts[5..], + operator.clone(), + operator_balance.clone(), + Some(system), + Some(treasury), + ); + + let mut excessive_lamports = 0_u64; - let message = holder.transaction(); - let trx = Transaction::from_rlp(&message)?; + let mut tag = crate::account::tag(program_id, &holder_or_storage)?; + if (tag == TAG_HOLDER_DEPRECATED) || (tag == TAG_STATE_FINALIZED_DEPRECATED) { + tag = crate::account::legacy::update_holder_account(&holder_or_storage)?; + } + + match tag { + TAG_HOLDER | TAG_HOLDER_DEPRECATED => { + let mut trx = { + let mut holder = Holder::from_account(program_id, holder_or_storage.clone())?; + + // We have to initialize the heap before creating Transaction object, but since + // transaction's rlp itself is stored in the holder account, we have two options: + // 1. Copy the rlp and initialize the heap right after the holder's header. + // This way, the space occupied by the rlp within holder will be reused. + // 2. Don't copy the rlp, initialize the heap after transaction rlp in the holder. + // The first option (chosen) saves the holder space in exchange for compute units. + // The second option wastes the holder space (because transaction bytes will be + // stored two times), but doesnt copy. + let transaction_rlp_copy = holder.transaction().to_vec(); + holder.init_heap(0)?; + holder.validate_owner(&operator)?; + + let trx = Transaction::from_rlp(&transaction_rlp_copy)?; holder.validate_transaction(&trx)?; trx }; + let origin = trx.recover_caller_address()?; - if trx.chain_id != expected_chain_id { - return Err(Error::InvalidChainId(trx.chain_id.unwrap_or(U256::ZERO))); - } - - solana_program::log::sol_log_data(&[b"HASH", &trx.hash]); + operator_balance.validate_transaction(&trx)?; + let miner_address = operator_balance.miner(origin); - let caller = trx.recover_caller_address()?; - let mut storage = - State::new(program_id, holder_or_storage_info, &accounts, caller, &trx)?; + log_data(&[b"HASH", &trx.hash]); + log_data(&[b"MINER", miner_address.as_bytes()]); - if expected_chain_id.is_none() { - let gas_multiplier = U256::from(GAS_LIMIT_MULTIPLIER_NO_CHAINID); - storage.gas_limit = storage.gas_limit.saturating_mul(gas_multiplier); + if increase_gas_limit { + assert!(trx.chain_id().is_none()); + trx.use_gas_limit_multiplier(); } - let mut gasometer = Gasometer::new(None, &accounts.operator)?; + let mut gasometer = Gasometer::new(U256::ZERO, &operator)?; gasometer.record_solana_transaction_cost(); - gasometer.record_address_lookup_table(accounts.all_accounts); - gasometer.record_iterative_overhead(); + gasometer.record_address_lookup_table(accounts); gasometer.record_write_to_holder(&trx); - do_begin(accounts, storage, account_storage, gasometer, trx, caller) + excessive_lamports += crate::account::legacy::update_legacy_accounts(&accounts_db)?; + gasometer.refund_lamports(excessive_lamports); + + let storage = + StateAccount::new(program_id, holder_or_storage, &accounts_db, origin, trx)?; + + do_begin(accounts_db, storage, gasometer) } - State::TAG => { - let (storage, _blocked_accounts) = State::restore( - program_id, - holder_or_storage_info, - &accounts.operator, - accounts.remaining_accounts, - false, - )?; - - solana_program::log::sol_log_data(&[b"HASH", &storage.transaction_hash]); - - let mut gasometer = Gasometer::new(Some(storage.gas_used), &accounts.operator)?; + TAG_STATE => { + let (storage, accounts_status) = + StateAccount::restore(program_id, &holder_or_storage, &accounts_db)?; + + operator_balance.validate_transaction(storage.trx())?; + let miner_address = operator_balance.miner(storage.trx_origin()); + + log_data(&[b"HASH", &storage.trx().hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); + + let mut gasometer = Gasometer::new(storage.gas_used(), &operator)?; gasometer.record_solana_transaction_cost(); - do_continue(step_count, accounts, storage, account_storage, gasometer) + let reset = accounts_status != AccountsStatus::Ok; + do_continue(step_count, accounts_db, storage, gasometer, reset) } - FinalizedState::TAG => Err(Error::StorageAccountFinalized), - _ => Err(Error::AccountInvalidTag( - *holder_or_storage_info.key, - Holder::TAG, - )), - } + TAG_STATE_FINALIZED | TAG_STATE_FINALIZED_DEPRECATED => Err(Error::StorageAccountFinalized), + _ => Err(Error::AccountInvalidTag(*holder_or_storage.key, TAG_HOLDER)), + }?; + + **operator.try_borrow_mut_lamports()? += excessive_lamports; + + Ok(()) } diff --git a/evm_loader/program/src/instruction/transaction_step_from_account_no_chainid.rs b/evm_loader/program/src/instruction/transaction_step_from_account_no_chainid.rs index 392779c4b..f911b998a 100644 --- a/evm_loader/program/src/instruction/transaction_step_from_account_no_chainid.rs +++ b/evm_loader/program/src/instruction/transaction_step_from_account_no_chainid.rs @@ -1,8 +1,4 @@ -use crate::account::{program, EthereumAccount, Operator, Treasury}; -use crate::account_storage::ProgramAccountStorage; use crate::error::Result; -use crate::instruction::transaction_step::Accounts; -use arrayref::array_ref; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; pub fn process<'a>( @@ -10,36 +6,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Begin or Continue Transaction from Account Without ChainId"); + log_msg!("Instruction: Begin or Continue Transaction from Account Without ChainId"); - let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); - let step_count = u64::from(u32::from_le_bytes(*array_ref![instruction, 4, 4])); - - let holder_or_storage_info = &accounts[0]; - - let accounts = Accounts { - operator: Operator::from_account(&accounts[1])?, - treasury: Treasury::from_account(program_id, treasury_index, &accounts[2])?, - operator_ether_account: EthereumAccount::from_account(program_id, &accounts[3])?, - system_program: program::System::from_account(&accounts[4])?, - neon_program: program::Neon::from_account(program_id, &accounts[5])?, - remaining_accounts: &accounts[6..], - all_accounts: accounts, - }; - - let mut account_storage = ProgramAccountStorage::new( - program_id, - &accounts.operator, - Some(&accounts.system_program), - accounts.remaining_accounts, - )?; - - super::transaction_step_from_account::execute( - program_id, - holder_or_storage_info, - accounts, - &mut account_storage, - step_count, - None, - ) + super::transaction_step_from_account::process_inner(program_id, accounts, instruction, true) } diff --git a/evm_loader/program/src/instruction/transaction_step_from_instruction.rs b/evm_loader/program/src/instruction/transaction_step_from_instruction.rs index 621243492..8de489773 100644 --- a/evm_loader/program/src/instruction/transaction_step_from_instruction.rs +++ b/evm_loader/program/src/instruction/transaction_step_from_instruction.rs @@ -1,10 +1,15 @@ -use crate::account::{program, EthereumAccount, FinalizedState, Holder, Operator, State, Treasury}; -use crate::account_storage::ProgramAccountStorage; +use crate::account::legacy::{TAG_HOLDER_DEPRECATED, TAG_STATE_FINALIZED_DEPRECATED}; +use crate::account::{ + program, AccountsDB, AccountsStatus, Holder, Operator, OperatorBalanceAccount, + OperatorBalanceValidator, StateAccount, Treasury, TAG_HOLDER, TAG_STATE, TAG_STATE_FINALIZED, +}; +use crate::debug::log_data; use crate::error::{Error, Result}; use crate::gasometer::Gasometer; -use crate::instruction::transaction_step::{do_begin, do_continue, Accounts}; +use crate::instruction::transaction_step::{do_begin, do_continue}; use crate::types::Transaction; use arrayref::array_ref; +use ethnum::U256; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; pub fn process<'a>( @@ -12,76 +17,84 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Begin or Continue Transaction from Instruction"); + log_msg!("Instruction: Begin or Continue Transaction from Instruction"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); let step_count = u64::from(u32::from_le_bytes(*array_ref![instruction, 4, 4])); // skip let unique_index = u32::from_le_bytes(*array_ref![instruction, 8, 4]); let message = &instruction[4 + 4 + 4..]; - let storage_info = &accounts[0]; - - let accounts = Accounts { - operator: Operator::from_account(&accounts[1])?, - treasury: Treasury::from_account(program_id, treasury_index, &accounts[2])?, - operator_ether_account: EthereumAccount::from_account(program_id, &accounts[3])?, - system_program: program::System::from_account(&accounts[4])?, - neon_program: program::Neon::from_account(program_id, &accounts[5])?, - remaining_accounts: &accounts[6..], - all_accounts: accounts, - }; - - let mut account_storage = ProgramAccountStorage::new( - program_id, - &accounts.operator, - Some(&accounts.system_program), - accounts.remaining_accounts, - )?; - - match crate::account::tag(program_id, storage_info)? { - Holder::TAG | FinalizedState::TAG => { + let storage_info = accounts[0].clone(); + + let operator = Operator::from_account(&accounts[1])?; + let treasury = Treasury::from_account(program_id, treasury_index, &accounts[2])?; + let operator_balance = OperatorBalanceAccount::try_from_account(program_id, &accounts[3])?; + let system = program::System::from_account(&accounts[4])?; + + operator_balance.validate_owner(&operator)?; + + let accounts_db = AccountsDB::new( + &accounts[5..], + operator.clone(), + operator_balance.clone(), + Some(system), + Some(treasury), + ); + + let mut excessive_lamports = 0_u64; + + let mut tag = crate::account::tag(program_id, &storage_info)?; + if (tag == TAG_HOLDER_DEPRECATED) || (tag == TAG_STATE_FINALIZED_DEPRECATED) { + tag = crate::account::legacy::update_holder_account(&storage_info)?; + } + + match tag { + TAG_HOLDER | TAG_STATE_FINALIZED => { + // Holder's method (fn init_heap) transforms TAG_STATE_FINALIZED into HOLDER + // and it breaks the logic (of throwing StorageAccountFinalized error). + // In this case, an associated function is used instead. + Holder::init_holder_heap(program_id, &mut storage_info.clone(), 0)?; + let trx = Transaction::from_rlp(message)?; - let caller = trx.recover_caller_address()?; + let origin = trx.recover_caller_address()?; - solana_program::log::sol_log_data(&[b"HASH", &trx.hash]); + operator_balance.validate_transaction(&trx)?; + let miner_address = operator_balance.miner(origin); - let storage = State::new(program_id, storage_info, &accounts, caller, &trx)?; + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); - let mut gasometer = Gasometer::new(None, &accounts.operator)?; + let mut gasometer = Gasometer::new(U256::ZERO, &operator)?; gasometer.record_solana_transaction_cost(); - gasometer.record_address_lookup_table(accounts.all_accounts); - gasometer.record_iterative_overhead(); - - do_begin( - accounts, - storage, - &mut account_storage, - gasometer, - trx, - caller, - ) + gasometer.record_address_lookup_table(accounts); + + excessive_lamports += crate::account::legacy::update_legacy_accounts(&accounts_db)?; + gasometer.refund_lamports(excessive_lamports); + + let storage = StateAccount::new(program_id, storage_info, &accounts_db, origin, trx)?; + + do_begin(accounts_db, storage, gasometer) } - State::TAG => { - let (storage, _blocked_accounts) = State::restore( - program_id, - storage_info, - &accounts.operator, - accounts.remaining_accounts, - false, - )?; - solana_program::log::sol_log_data(&[b"HASH", &storage.transaction_hash]); - - let mut gasometer = Gasometer::new(Some(storage.gas_used), &accounts.operator)?; + TAG_STATE => { + let (storage, accounts_status) = + StateAccount::restore(program_id, &storage_info, &accounts_db)?; + + operator_balance.validate_transaction(storage.trx())?; + let miner_address = operator_balance.miner(storage.trx_origin()); + + log_data(&[b"HASH", &storage.trx().hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); + + let mut gasometer = Gasometer::new(storage.gas_used(), &operator)?; gasometer.record_solana_transaction_cost(); - do_continue( - step_count, - accounts, - storage, - &mut account_storage, - gasometer, - ) + let reset = accounts_status != AccountsStatus::Ok; + do_continue(step_count, accounts_db, storage, gasometer, reset) } - _ => Err(Error::AccountInvalidTag(*storage_info.key, Holder::TAG)), - } + _ => Err(Error::AccountInvalidTag(*storage_info.key, TAG_HOLDER)), + }?; + + **operator.try_borrow_mut_lamports()? += excessive_lamports; + + Ok(()) } diff --git a/evm_loader/program/src/lib.rs b/evm_loader/program/src/lib.rs index aaa79338a..561bb908f 100644 --- a/evm_loader/program/src/lib.rs +++ b/evm_loader/program/src/lib.rs @@ -6,10 +6,13 @@ #![allow( clippy::module_name_repetitions, clippy::missing_const_for_fn, - clippy::use_self + clippy::use_self, + clippy::future_not_send )] #![allow(missing_docs, clippy::missing_panics_doc, clippy::missing_errors_doc)] +solana_program::declare_id!(crate::config::PROGRAM_ID); + mod allocator; #[macro_use] mod debug; @@ -18,13 +21,15 @@ pub mod error; pub mod account; pub mod account_storage; pub mod config; +#[cfg(target_os = "solana")] pub mod entrypoint; pub mod evm; pub mod executor; pub mod external_programs; pub mod gasometer; +#[cfg(target_os = "solana")] pub mod instruction; -pub mod state_account; +#[macro_use] pub mod types; // Export current solana-sdk types for downstream users who may also be building with a different diff --git a/evm_loader/program/src/state_account.rs b/evm_loader/program/src/state_account.rs deleted file mode 100644 index c0328ebce..000000000 --- a/evm_loader/program/src/state_account.rs +++ /dev/null @@ -1,314 +0,0 @@ -use crate::{ - account::{program, EthereumAccount, FinalizedState, Holder, Incinerator, Operator, State}, - config::OPERATOR_PRIORITY_SLOTS, - error::Error, - types::{Address, Transaction}, -}; -use ethnum::U256; -use solana_program::{ - account_info::AccountInfo, clock::Clock, program_error::ProgramError, pubkey::Pubkey, - sysvar::Sysvar, -}; -use std::cell::{Ref, RefMut}; - -const ACCOUNT_CHUNK_LEN: usize = 1 + 1 + 32; - -pub enum Deposit<'a> { - ReturnToOperator(Operator<'a>), - Burn(Incinerator<'a>), -} - -pub struct BlockedAccountMeta { - pub key: Pubkey, - pub exists: bool, - pub is_writable: bool, -} - -pub type BlockedAccounts = Vec; - -impl<'a> FinalizedState<'a> { - #[must_use] - pub fn is_outdated(&self, transaction_hash: &[u8; 32]) -> bool { - self.transaction_hash.ne(transaction_hash) - } -} - -impl<'a> State<'a> { - pub fn new( - program_id: &'a Pubkey, - info: &'a AccountInfo<'a>, - accounts: &crate::instruction::transaction_step::Accounts<'a>, - caller: Address, - trx: &Transaction, - ) -> Result { - let owner = match crate::account::tag(program_id, info)? { - Holder::TAG => { - let holder = Holder::from_account(program_id, info)?; - holder.owner - } - FinalizedState::TAG => { - let finalized_storage = FinalizedState::from_account(program_id, info)?; - if !finalized_storage.is_outdated(&trx.hash) { - return Err!(Error::StorageAccountFinalized.into(); "Transaction already finalized"); - } - - finalized_storage.owner - } - _ => { - return Err!(ProgramError::InvalidAccountData; "Account {} - expected finalized storage or holder", info.key) - } - }; - - if &owner != accounts.operator.key { - return Err!(ProgramError::InvalidAccountData; "Account {} - invalid state account owner", info.key); - } - - let data = crate::account::state::Data { - owner, - transaction_hash: trx.hash, - caller, - gas_limit: trx.gas_limit, - gas_price: trx.gas_price, - gas_used: U256::ZERO, - operator: *accounts.operator.key, - slot: Clock::get()?.slot, - accounts_len: accounts.remaining_accounts.len(), - evm_state_len: 0, - evm_machine_len: 0, - }; - - info.data.borrow_mut()[0] = 0_u8; - let mut storage = State::init(program_id, info, data)?; - - storage.make_deposit(&accounts.system_program, &accounts.operator)?; - storage.write_blocked_accounts(program_id, accounts.remaining_accounts)?; - Ok(storage) - } - - pub fn restore( - program_id: &Pubkey, - info: &'a AccountInfo<'a>, - operator: &Operator, - remaining_accounts: &[AccountInfo], - is_cancelling: bool, - ) -> Result<(Self, BlockedAccounts), ProgramError> { - let account_tag = crate::account::tag(program_id, info)?; - if account_tag == FinalizedState::TAG { - return Err!(Error::StorageAccountFinalized.into(); "Account {} - Storage Finalized", info.key); - } - if account_tag == Holder::TAG { - return Err!(Error::StorageAccountUninitialized.into(); "Account {} - Storage Uninitialized", info.key); - } - - let mut storage = State::from_account(program_id, info)?; - let blocked_accounts = - storage.check_blocked_accounts(program_id, remaining_accounts, is_cancelling)?; - - let clock = Clock::get()?; - if (*operator.key != storage.operator) - && ((clock.slot - storage.slot) <= OPERATOR_PRIORITY_SLOTS) - { - return Err!(ProgramError::InvalidAccountData; "operator.key != storage.operator"); - } - - if storage.operator != *operator.key { - storage.operator = *operator.key; - storage.slot = clock.slot; - } - - Ok((storage, blocked_accounts)) - } - - pub fn finalize(self, deposit: Deposit<'a>) -> Result, ProgramError> { - debug_print!("Finalize Storage {}", self.info.key); - - match deposit { - Deposit::ReturnToOperator(operator) => self.withdraw_deposit(&operator), - Deposit::Burn(incinerator) => self.withdraw_deposit(&incinerator), - }?; - - let finalized_data = crate::account::state::FinalizedData { - owner: self.owner, - transaction_hash: self.transaction_hash, - }; - - let finalized = unsafe { self.replace(finalized_data) }?; - Ok(finalized) - } - - fn make_deposit( - &self, - system_program: &program::System<'a>, - source: &Operator<'a>, - ) -> Result<(), ProgramError> { - system_program.transfer(source, self.info, crate::config::PAYMENT_TO_DEPOSIT) - } - - fn withdraw_deposit(&self, target: &AccountInfo<'a>) -> Result<(), ProgramError> { - let source_lamports = self - .info - .lamports() - .checked_sub(crate::config::PAYMENT_TO_DEPOSIT) - .ok_or_else( - || E!(ProgramError::InvalidArgument; "Deposit source lamports underflow"), - )?; - - let target_lamports = target - .lamports() - .checked_add(crate::config::PAYMENT_TO_DEPOSIT) - .ok_or_else(|| E!(ProgramError::InvalidArgument; "Deposit target lamports overflow"))?; - - **self.info.lamports.borrow_mut() = source_lamports; - **target.lamports.borrow_mut() = target_lamports; - - Ok(()) - } - - pub fn read_blocked_accounts(&self) -> Result { - let (begin, end) = self.blocked_accounts_region(); - - let account_data = self.info.try_borrow_data()?; - if account_data.len() < end { - return Err!(ProgramError::AccountDataTooSmall; "Account {} - data too small, required: {}", self.info.key, end); - } - - let keys_storage = &account_data[begin..end]; - let chunks = keys_storage.chunks_exact(ACCOUNT_CHUNK_LEN); - let accounts = chunks - .map(|c| c.split_at(2)) - .map(|(meta, key)| BlockedAccountMeta { - key: Pubkey::try_from(key).expect("key is 32 bytes"), - exists: meta[1] != 0, - is_writable: meta[0] != 0, - }) - .collect(); - - Ok(accounts) - } - - fn write_blocked_accounts( - &mut self, - program_id: &Pubkey, - accounts: &[AccountInfo], - ) -> Result<(), ProgramError> { - assert_eq!(self.accounts_len, accounts.len()); // should be always true - - let (begin, end) = self.blocked_accounts_region(); - - let mut account_data = self.info.try_borrow_mut_data()?; - if account_data.len() < end { - return Err!(ProgramError::AccountDataTooSmall; "Account {} - data too small, required: {}", self.info.key, end); - } - - let accounts_storage = &mut account_data[begin..end]; - let accounts_storage = accounts_storage.chunks_exact_mut(ACCOUNT_CHUNK_LEN); - for (info, account_storage) in accounts.iter().zip(accounts_storage) { - account_storage[0] = u8::from(info.is_writable); - account_storage[1] = u8::from(Self::account_exists(program_id, info)); - account_storage[2..].copy_from_slice(info.key.as_ref()); - } - - Ok(()) - } - - pub fn update_blocked_accounts(&mut self, accounts: I) -> Result<(), Error> - where - I: ExactSizeIterator, - { - let evm_data_len = self.evm_state_len + self.evm_machine_len; - let (evm_data_offset, _) = self.evm_data_region(); - let evm_data_range = evm_data_offset..evm_data_offset + evm_data_len; - - self.accounts_len = accounts.len(); - let (accounts_begin, accounts_end) = self.blocked_accounts_region(); - - let mut data = self.info.try_borrow_mut_data()?; - // Move EVM data - data.copy_within(evm_data_range, accounts_end); - - // Write accounts - let accounts_storage = &mut data[accounts_begin..accounts_end]; - let accounts_storage = accounts_storage.chunks_exact_mut(ACCOUNT_CHUNK_LEN); - for (meta, account_storage) in accounts.zip(accounts_storage) { - account_storage[0] = u8::from(meta.is_writable); - account_storage[1] = u8::from(meta.exists); - account_storage[2..].copy_from_slice(meta.key.as_ref()); - } - - Ok(()) - } - - fn check_blocked_accounts( - &self, - program_id: &Pubkey, - remaining_accounts: &[AccountInfo], - is_cancelling: bool, - ) -> Result { - let blocked_accounts = self.read_blocked_accounts()?; - if blocked_accounts.len() != remaining_accounts.len() { - return Err!(ProgramError::NotEnoughAccountKeys; "Invalid number of accounts"); - } - - for (blocked, info) in blocked_accounts.iter().zip(remaining_accounts) { - if blocked.key != *info.key { - return Err!(ProgramError::InvalidAccountData; "Expected account {}, found {}", blocked.key, info.key); - } - - if blocked.is_writable && !info.is_writable { - return Err!(ProgramError::InvalidAccountData; "Expected account {} is_writable: {}", info.key, blocked.is_writable); - } - - if !is_cancelling && !blocked.exists && Self::account_exists(program_id, info) { - return Err!( - ProgramError::AccountAlreadyInitialized; - "Blocked nonexistent account {} was created/initialized outside current transaction. \ - Transaction is being cancelled in order to prevent possible data corruption.", - info.key - ); - } - } - - Ok(blocked_accounts) - } - - #[must_use] - pub fn evm_data(&self) -> Ref<[u8]> { - let (begin, end) = self.evm_data_region(); - - let data = self.info.data.borrow(); - Ref::map(data, |d| &d[begin..end]) - } - - #[must_use] - pub fn evm_data_mut(&mut self) -> RefMut<[u8]> { - let (begin, end) = self.evm_data_region(); - - let data = self.info.data.borrow_mut(); - RefMut::map(data, |d| &mut d[begin..end]) - } - - #[must_use] - fn evm_data_region(&self) -> (usize, usize) { - let (_, accounts_region_end) = self.blocked_accounts_region(); - - let begin = accounts_region_end; - let end = self.info.data_len(); - - (begin, end) - } - - #[must_use] - fn blocked_accounts_region(&self) -> (usize, usize) { - let begin = Self::SIZE; - let end = begin + self.accounts_len * ACCOUNT_CHUNK_LEN; - - (begin, end) - } - - #[must_use] - fn account_exists(program_id: &Pubkey, info: &AccountInfo) -> bool { - (info.owner == program_id) - && !info.data_is_empty() - && (info.data.borrow()[0] == EthereumAccount::TAG) - } -} diff --git a/evm_loader/program/src/types/address.rs b/evm_loader/program/src/types/address.rs index 34aab8dfa..48ea697a1 100644 --- a/evm_loader/program/src/types/address.rs +++ b/evm_loader/program/src/types/address.rs @@ -1,3 +1,4 @@ +use ethnum::U256; use hex::FromHex; use serde::{Deserialize, Serialize}; use solana_program::pubkey::Pubkey; @@ -5,7 +6,7 @@ use std::convert::{From, TryInto}; use std::fmt::{Debug, Display}; use std::str::FromStr; -use crate::account::ACCOUNT_SEED_VERSION; +use crate::account::{Operator, ACCOUNT_SEED_VERSION}; use crate::error::Error; #[repr(transparent)] @@ -57,6 +58,32 @@ impl Address { let seeds: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], &self.0]; Pubkey::find_program_address(seeds, program_id) } + + #[must_use] + pub fn find_balance_address(&self, program_id: &Pubkey, chain_id: u64) -> (Pubkey, u8) { + let chain_id = U256::from(chain_id); + + let seeds: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], &self.0, &chain_id.to_be_bytes()]; + Pubkey::find_program_address(seeds, program_id) + } + + #[must_use] + pub fn find_operator_address( + &self, + program_id: &Pubkey, + chain_id: u64, + operator: &Operator, + ) -> (Pubkey, u8) { + let chain_id = U256::from(chain_id); + + let seeds: &[&[u8]] = &[ + &[ACCOUNT_SEED_VERSION], + operator.key.as_ref(), + &self.0, + &chain_id.to_be_bytes(), + ]; + Pubkey::find_program_address(seeds, program_id) + } } impl FromStr for Address { diff --git a/evm_loader/program/src/types/boxx.rs b/evm_loader/program/src/types/boxx.rs new file mode 100644 index 000000000..15bb4f498 --- /dev/null +++ b/evm_loader/program/src/types/boxx.rs @@ -0,0 +1,7 @@ +use crate::allocator::{acc_allocator, StateAccountAllocator}; + +pub type Boxx = allocator_api2::boxed::Box; + +pub fn boxx(value: T) -> Boxx { + Boxx::new_in(value, acc_allocator()) +} diff --git a/evm_loader/program/src/types/mod.rs b/evm_loader/program/src/types/mod.rs index 0cdaebdd9..d6e7fff22 100644 --- a/evm_loader/program/src/types/mod.rs +++ b/evm_loader/program/src/types/mod.rs @@ -1,5 +1,17 @@ pub use address::Address; +pub use transaction::AccessListTx; +pub use transaction::DynamicFeeTx; +pub use transaction::LegacyTx; +pub use transaction::StorageKey; pub use transaction::Transaction; +pub use transaction::TransactionPayload; +pub use tree_map::TreeMap; +pub use vector::Vector; mod address; mod transaction; +pub mod tree_map; +#[macro_use] +pub mod vector; +pub mod boxx; +pub mod read_raw_utils; diff --git a/evm_loader/program/src/types/read_raw_utils.rs b/evm_loader/program/src/types/read_raw_utils.rs new file mode 100644 index 000000000..43cabd734 --- /dev/null +++ b/evm_loader/program/src/types/read_raw_utils.rs @@ -0,0 +1,37 @@ +use std::cmp::{max, min}; +use std::ptr::read_unaligned; +use std::slice; + +pub trait ReconstructRaw { + /// # Safety + /// Function to reconstruct an object from the memory. Should be used by macro and not implemented manually. + /// Also, it must not be used in the EVM Program, only in the Core API. + unsafe fn build(ptr: *const Self, offset: isize) -> Self; +} + +/// Reading the raw memory bits to reconstuct the Vec type. +/// # Safety +/// Low level reads in the memory with offsets to reconstruct the vector. +#[must_use] +pub unsafe fn read_vec(vec_start_ptr: *const usize, offset: isize) -> Vec { + // 1. The Vector's memory layout consists of three usizes: ptr to the buffer, capacity and length. + // 2. There's no alignment between the fields, the Vector occupies exactly the 3*sizeof bytes. + // 3. The order of those fields in the memory is unspecified (no repr is set on the vec struct). + // => The len is the smallest of those three usizes, because it can't realistically be more than the buffer + // ptr value and it's no more than capacity. + // => The buffer ptr is the biggest among them. + let vec_parts = ( + read_unaligned(vec_start_ptr), + read_unaligned(vec_start_ptr.add(1)), + read_unaligned(vec_start_ptr.add(2)), + ); + let vec_len = min(min(vec_parts.0, vec_parts.1), vec_parts.2); + let vec_buf_ptr_unadjusted = max(max(vec_parts.0, vec_parts.1), vec_parts.2) as *const u8; + // Offset the buffer pointer from the state account allocator memory space into the current allocator. + let vec_buf_ptr_adjusted = vec_buf_ptr_unadjusted.offset(offset).cast::().cast_mut(); + + // Allocate a new vec and with the exact number of elements and copy the memory. + let mut res_vec = vec![T::default(); vec_len]; + res_vec.copy_from_slice(slice::from_raw_parts(vec_buf_ptr_adjusted, vec_len)); + res_vec +} diff --git a/evm_loader/program/src/types/transaction.rs b/evm_loader/program/src/types/transaction.rs index 63c17ef3e..b04c319b3 100644 --- a/evm_loader/program/src/types/transaction.rs +++ b/evm_loader/program/src/types/transaction.rs @@ -1,52 +1,108 @@ -use crate::error::Error; use ethnum::U256; +use maybe_async::maybe_async; +use rlp::{DecoderError, Rlp}; +use serde::{Deserialize, Serialize}; +use solana_program::instruction::{get_stack_height, TRANSACTION_LEVEL_STACK_HEIGHT}; use std::convert::TryInto; -use super::Address; +use crate::types::vector::VectorVecExt; +use crate::{ + account_storage::AccountStorage, config::GAS_LIMIT_MULTIPLIER_NO_CHAINID, error::Error, vector, +}; -#[derive(Debug, Default, Clone)] -pub struct Transaction { +use super::vector::VectorSliceExt; +use super::{Address, Vector}; + +use super::read_raw_utils::ReconstructRaw; +use crate::types::read_raw_utils::read_vec; +use evm_loader_macro::ReconstructRaw; + +#[repr(transparent)] +#[derive( + Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, +)] +pub struct StorageKey([u8; 32]); + +impl rlp::Decodable for StorageKey { + fn decode(rlp: &rlp::Rlp) -> Result { + rlp.decoder().decode_value(|bytes| { + let array: [u8; 32] = bytes + .try_into() + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?; + Ok(Self(array)) + }) + } +} + +impl TryFrom> for StorageKey { + type Error = String; + + fn try_from(hex: Vec) -> Result { + let bytes = hex; + + if bytes.len() != 32 { + return Err(String::from("Hex string must be 32 bytes")); + } + + let mut array = [0; 32]; + array.copy_from_slice(&bytes); + + Ok(StorageKey(array)) + } +} + +impl AsRef<[u8]> for StorageKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +fn decode_byte_vector(rlp: &Rlp) -> Result, DecoderError> { + rlp.decoder().decode_value(|bytes| Ok(bytes.to_vector())) +} + +#[derive(Debug, Clone)] +pub enum TransactionEnvelope { + Legacy, + AccessList, + DynamicFee, +} + +impl TransactionEnvelope { + pub fn get_type(bytes: &[u8]) -> (Option, &[u8]) { + // Legacy transaction format + if rlp::Rlp::new(bytes).is_list() { + (None, bytes) + // It's an EIP-2718 typed TX envelope. + } else { + match bytes[0] { + 0x00 => (Some(TransactionEnvelope::Legacy), &bytes[1..]), + 0x01 => (Some(TransactionEnvelope::AccessList), &bytes[1..]), + 0x02 => (Some(TransactionEnvelope::DynamicFee), &bytes[1..]), + byte => panic!("Unsupported EIP-2718 Transaction type | First byte: {byte}"), + } + } + } +} + +#[derive(Debug, ReconstructRaw)] +#[repr(C)] +pub struct LegacyTx { pub nonce: u64, pub gas_price: U256, pub gas_limit: U256, pub target: Option
, pub value: U256, - pub call_data: crate::evm::Buffer, + pub call_data: Vector, pub v: U256, pub r: U256, pub s: U256, pub chain_id: Option, pub recovery_id: u8, - pub rlp_len: usize, - pub hash: [u8; 32], - pub signed_hash: [u8; 32], -} - -impl Transaction { - pub fn from_rlp(transaction: &[u8]) -> Result { - rlp::decode(transaction).map_err(Error::from) - } - - pub fn recover_caller_address(&self) -> Result { - use solana_program::keccak::{hash, Hash}; - use solana_program::secp256k1_recover::secp256k1_recover; - - let signature = [self.r.to_be_bytes(), self.s.to_be_bytes()].concat(); - let public_key = secp256k1_recover(&self.signed_hash, self.recovery_id, &signature)?; - - let Hash(address) = hash(&public_key.to_bytes()); - let address: [u8; 20] = address[12..32].try_into()?; - - Ok(Address::from(address)) - } } -impl rlp::Decodable for Transaction { +impl rlp::Decodable for LegacyTx { fn decode(rlp: &rlp::Rlp) -> Result { - if !rlp.is_list() { - return Err(rlp::DecoderError::RlpExpectedToBeList); - } - let rlp_len = { let info = rlp.payload_info()?; info.header_len + info.value_len @@ -72,7 +128,7 @@ impl rlp::Decodable for Transaction { } }; let value: U256 = u256(&rlp.at(4)?)?; - let call_data = crate::evm::Buffer::from_slice(rlp.at(5)?.data()?); + let call_data = decode_byte_vector(&rlp.at(5)?)?; let v: U256 = u256(&rlp.at(6)?)?; let r: U256 = u256(&rlp.at(7)?)?; let s: U256 = u256(&rlp.at(8)?)?; @@ -93,10 +149,7 @@ impl rlp::Decodable for Transaction { return Err(rlp::DecoderError::RlpExpectedToBeData); }; - let hash = solana_program::keccak::hash(rlp.as_raw()).to_bytes(); - let signed_hash = signed_hash(rlp, chain_id)?; - - let tx = Self { + let tx = LegacyTx { nonce, gas_price, gas_limit, @@ -108,79 +161,675 @@ impl rlp::Decodable for Transaction { s, chain_id, recovery_id, - rlp_len, - hash, - signed_hash, }; Ok(tx) } } -fn signed_hash( - transaction: &rlp::Rlp, - chain_id: Option, -) -> Result<[u8; 32], rlp::DecoderError> { - let raw = transaction.as_raw(); - let payload_info = transaction.payload_info()?; - let (_, v_offset) = transaction.at_with_offset(6)?; - - let middle = &raw[payload_info.header_len..v_offset]; +#[derive(Debug, ReconstructRaw)] +#[repr(C)] +pub struct AccessListTx { + pub nonce: u64, + pub gas_price: U256, + pub gas_limit: U256, + pub target: Option
, + pub value: U256, + pub call_data: Vector, + pub r: U256, + pub s: U256, + pub chain_id: U256, + pub recovery_id: u8, + pub access_list: Vector<(Address, Vector)>, +} - let trailer = chain_id.map_or_else(Vec::new, |chain_id| { - let chain_id = { - let leading_empty_bytes = (chain_id.leading_zeros() as usize) / 8; - let bytes = chain_id.to_be_bytes(); - bytes[leading_empty_bytes..].to_vec() +impl rlp::Decodable for AccessListTx { + fn decode(rlp: &rlp::Rlp) -> Result { + let rlp_len = { + let info = rlp.payload_info()?; + info.header_len + info.value_len }; - let mut trailer = Vec::with_capacity(64); - match chain_id.len() { - 0 => { - trailer.extend_from_slice(&[0x80]); + if rlp.as_raw().len() != rlp_len { + return Err(rlp::DecoderError::RlpInconsistentLengthAndData); + } + + let chain_id: U256 = u256(&rlp.at(0)?)?; + let nonce: u64 = rlp.val_at(1)?; + let gas_price: U256 = u256(&rlp.at(2)?)?; + let gas_limit: U256 = u256(&rlp.at(3)?)?; + let target: Option
= { + let target = rlp.at(4)?; + if target.is_empty() { + if target.is_data() { + None + } else { + return Err(rlp::DecoderError::RlpExpectedToBeData); + } + } else { + Some(target.as_val()?) } - 1 if chain_id[0] < 0x80 => { - trailer.extend_from_slice(&chain_id); + }; + + let value: U256 = u256(&rlp.at(5)?)?; + let call_data = decode_byte_vector(&rlp.at(6)?)?; + + let rlp_access_list = rlp.at(7)?; + let mut access_list = vector![]; + + for entry in &rlp_access_list { + // Check if entry is a list + if entry.is_list() { + // Parse address from first element + let address: Address = entry.at(0)?.as_val()?; + + // Get storage keys from second element + let mut storage_keys: Vector = vector![]; + + for key in &entry.at(1)? { + storage_keys.push(key.as_val()?); + } + + access_list.push((address, storage_keys)); + } else { + return Err(rlp::DecoderError::RlpExpectedToBeList); } - len @ 1..=55 => { - let len: u8 = len.try_into().unwrap(); + } + + let y_parity: u8 = rlp.at(8)?.as_val()?; + let r: U256 = u256(&rlp.at(9)?)?; + let s: U256 = u256(&rlp.at(10)?)?; + + if rlp.at(11).is_ok() { + return Err(rlp::DecoderError::RlpIncorrectListLen); + } + + let tx = AccessListTx { + nonce, + gas_price, + gas_limit, + target, + value, + call_data, + r, + s, + chain_id, + recovery_id: y_parity, + access_list, + }; + + Ok(tx) + } +} + +#[derive(Debug, ReconstructRaw)] +#[repr(C)] +pub struct DynamicFeeTx { + pub nonce: u64, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas_limit: U256, + pub target: Option
, + pub value: U256, + pub call_data: Vector, + pub r: U256, + pub s: U256, + pub chain_id: U256, + pub recovery_id: u8, + pub access_list: Vector<(Address, Vector)>, +} + +impl rlp::Decodable for DynamicFeeTx { + fn decode(rlp: &rlp::Rlp) -> Result { + let rlp_len = { + let info = rlp.payload_info()?; + info.header_len + info.value_len + }; + + if rlp.as_raw().len() != rlp_len { + return Err(rlp::DecoderError::RlpInconsistentLengthAndData); + } + + let chain_id: U256 = u256(&rlp.at(0)?)?; + let nonce: u64 = rlp.val_at(1)?; - trailer.extend_from_slice(&[0x80 + len]); - trailer.extend_from_slice(&chain_id); + let max_priority_fee_per_gas: U256 = u256(&rlp.at(2)?)?; + let max_fee_per_gas: U256 = u256(&rlp.at(3)?)?; + if max_fee_per_gas < max_priority_fee_per_gas { + return Err(rlp::DecoderError::Custom( + "max_fee_per_gas < max_priority_fee_per_gas", + )); + } + + let gas_limit: U256 = u256(&rlp.at(4)?)?; + let target: Option
= { + let target = rlp.at(5)?; + if target.is_empty() { + if target.is_data() { + None + } else { + return Err(rlp::DecoderError::RlpExpectedToBeData); + } + } else { + Some(target.as_val()?) } - _ => { - unreachable!("chain_id.len() <= 32") + }; + + let value: U256 = u256(&rlp.at(6)?)?; + let call_data = decode_byte_vector(&rlp.at(7)?)?; + + let rlp_access_list = rlp.at(8)?; + let mut access_list = vector![]; + + for entry in &rlp_access_list { + // Check if entry is a list + if entry.is_list() { + // Parse address from first element + let address: Address = entry.at(0)?.as_val()?; + + // Get storage keys from second element + let mut storage_keys: Vector = vector![]; + + for key in &entry.at(1)? { + storage_keys.push(key.as_val()?); + } + + access_list.push((address, storage_keys)); + } else { + return Err(rlp::DecoderError::RlpExpectedToBeList); } } - trailer.extend_from_slice(&[0x80, 0x80]); - trailer - }); + let y_parity: u8 = rlp.at(9)?.as_val()?; + let r: U256 = u256(&rlp.at(10)?)?; + let s: U256 = u256(&rlp.at(11)?)?; + + if rlp.at(12).is_ok() { + return Err(rlp::DecoderError::RlpIncorrectListLen); + } - let header: Vec = { - let len = middle.len() + trailer.len(); - if len <= 55 { - let len: u8 = len.try_into().unwrap(); - vec![0xC0 + len] + let tx = DynamicFeeTx { + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + target, + value, + call_data, + r, + s, + chain_id, + recovery_id: y_parity, + access_list, + }; + + Ok(tx) + } +} + +#[derive(Debug)] +#[repr(C, u8)] +pub enum TransactionPayload { + Legacy(LegacyTx), + AccessList(AccessListTx), + DynamicFee(DynamicFeeTx), +} + +#[derive(Debug)] +#[repr(C)] +pub struct Transaction { + pub transaction: TransactionPayload, + pub byte_len: usize, + pub hash: [u8; 32], + pub signed_hash: [u8; 32], +} + +impl Transaction { + pub fn from_payload( + transaction_type: &Option, + chain_id: Option, + transaction_rlp: &rlp::Rlp, + transaction: TransactionPayload, + ) -> Result { + let (hash, signed_hash) = match *transaction_type { + // Legacy transaction wrapped in envelop + Some(TransactionEnvelope::Legacy) => { + let hash = + solana_program::keccak::hashv(&[&[0x00], transaction_rlp.as_raw()]).to_bytes(); + let signed_hash = Self::calculate_legacy_signature(transaction_rlp, chain_id)?; + + (hash, signed_hash) + } + // Access List transaction + Some(TransactionEnvelope::AccessList) => { + let hash = + solana_program::keccak::hashv(&[&[0x01], transaction_rlp.as_raw()]).to_bytes(); + let signed_hash = Self::eip2718_signed_hash(&[0x01], transaction_rlp, 8)?; + + (hash, signed_hash) + } + // Dynamic Fee transaction + Some(TransactionEnvelope::DynamicFee) => { + let hash = + solana_program::keccak::hashv(&[&[0x02], transaction_rlp.as_raw()]).to_bytes(); + let signed_hash = Self::eip2718_signed_hash(&[0x02], transaction_rlp, 9)?; + + (hash, signed_hash) + } + // Legacy trasaction + None => { + let hash = solana_program::keccak::hash(transaction_rlp.as_raw()).to_bytes(); + let signed_hash = Self::calculate_legacy_signature(transaction_rlp, chain_id)?; + + (hash, signed_hash) + } + }; + + let info = transaction_rlp.payload_info()?; + let byte_len = if transaction_type.is_none() { + // Legacy transaction + info.header_len + info.value_len } else { - let len_bytes = { - let leading_empty_bytes = (len.leading_zeros() as usize) / 8; - let bytes = len.to_be_bytes(); + // Transaction in the type envelope + info.header_len + info.value_len + 1 // + 1 byte for type + }; + + Ok(Transaction { + transaction, + byte_len, + hash, + signed_hash, + }) + } + + fn eip2718_signed_hash( + transaction_type: &[u8], + transaction: &rlp::Rlp, + middle_offset: usize, + ) -> Result<[u8; 32], rlp::DecoderError> { + let raw = transaction.as_raw(); + let payload_info = transaction.payload_info()?; + let (_, middle_offset) = transaction.at_with_offset(middle_offset)?; + + let body = &raw[payload_info.header_len..middle_offset]; + + let header: Vec = { + let len = body.len(); + if len <= 55 { + let len: u8 = len.try_into().unwrap(); + vec![0xC0 + len] + } else { + let len_bytes = { + let leading_empty_bytes = (len.leading_zeros() as usize) / 8; + let bytes = len.to_be_bytes(); + bytes[leading_empty_bytes..].to_vec() + }; + let len_bytes_len: u8 = len_bytes.len().try_into().unwrap(); + + let mut header = Vec::with_capacity(10); + header.extend_from_slice(&[0xF7 + len_bytes_len]); + header.extend_from_slice(&len_bytes); + + header + } + }; + + let hash = solana_program::keccak::hashv(&[transaction_type, &header, body]).to_bytes(); + + Ok(hash) + } + + fn calculate_legacy_signature( + transaction: &rlp::Rlp, + chain_id: Option, + ) -> Result<[u8; 32], rlp::DecoderError> { + let raw = transaction.as_raw(); + let payload_info = transaction.payload_info()?; + let (_, v_offset) = transaction.at_with_offset(6)?; + + let middle = &raw[payload_info.header_len..v_offset]; + + let trailer = chain_id.map_or_else(Vec::new, |chain_id| { + let chain_id = { + let leading_empty_bytes = (chain_id.leading_zeros() as usize) / 8; + let bytes = chain_id.to_be_bytes(); bytes[leading_empty_bytes..].to_vec() }; - let len_bytes_len: u8 = len_bytes.len().try_into().unwrap(); - let mut header = Vec::with_capacity(10); - header.extend_from_slice(&[0xF7 + len_bytes_len]); - header.extend_from_slice(&len_bytes); + let mut trailer = Vec::with_capacity(64); + match chain_id.len() { + 0 => { + trailer.extend_from_slice(&[0x80]); + } + 1 if chain_id[0] < 0x80 => { + trailer.extend_from_slice(&chain_id); + } + len @ 1..=55 => { + let len: u8 = len.try_into().unwrap(); + + trailer.extend_from_slice(&[0x80 + len]); + trailer.extend_from_slice(&chain_id); + } + _ => { + unreachable!("chain_id.len() <= 32") + } + } + + trailer.extend_from_slice(&[0x80, 0x80]); + trailer + }); + + let header: Vec = { + let len = middle.len() + trailer.len(); + if len <= 55 { + let len: u8 = len.try_into().unwrap(); + vec![0xC0 + len] + } else { + let len_bytes = { + let leading_empty_bytes = (len.leading_zeros() as usize) / 8; + let bytes = len.to_be_bytes(); + bytes[leading_empty_bytes..].to_vec() + }; + let len_bytes_len: u8 = len_bytes.len().try_into().unwrap(); - header + let mut header = Vec::with_capacity(10); + header.extend_from_slice(&[0xF7 + len_bytes_len]); + header.extend_from_slice(&len_bytes); + + header + } + }; + + let hash = solana_program::keccak::hashv(&[&header, middle, &trailer]).to_bytes(); + + Ok(hash) + } +} + +impl Transaction { + pub fn from_rlp(transaction: &[u8]) -> Result { + let (transaction_type, transaction) = TransactionEnvelope::get_type(transaction); + + let tx = match transaction_type { + Some(TransactionEnvelope::Legacy) => { + let legacy_tx = rlp::decode::(transaction).map_err(Error::from)?; + let chain_id = legacy_tx.chain_id; + let tx = TransactionPayload::Legacy(legacy_tx); + Transaction::from_payload( + &Some(TransactionEnvelope::Legacy), + chain_id, + &rlp::Rlp::new(transaction), + tx, + )? + } + Some(TransactionEnvelope::AccessList) => { + let access_list_tx = + rlp::decode::(transaction).map_err(Error::from)?; + let chain_id = access_list_tx.chain_id; + let tx = TransactionPayload::AccessList(access_list_tx); + Transaction::from_payload( + &Some(TransactionEnvelope::AccessList), + Some(chain_id), + &rlp::Rlp::new(transaction), + tx, + )? + } + Some(TransactionEnvelope::DynamicFee) => { + let dynamic_fee_tx = + rlp::decode::(transaction).map_err(Error::from)?; + let chain_id = dynamic_fee_tx.chain_id; + let tx = TransactionPayload::DynamicFee(dynamic_fee_tx); + Transaction::from_payload( + &Some(TransactionEnvelope::DynamicFee), + Some(chain_id), + &rlp::Rlp::new(transaction), + tx, + )? + } + None => { + let legacy_tx = rlp::decode::(transaction).map_err(Error::from)?; + let chain_id = legacy_tx.chain_id; + let tx = TransactionPayload::Legacy(legacy_tx); + Transaction::from_payload(&None, chain_id, &rlp::Rlp::new(transaction), tx)? + } + }; + + Ok(tx) + } + + pub fn recover_caller_address(&self) -> Result { + use solana_program::keccak::{hash, Hash}; + use solana_program::secp256k1_recover::secp256k1_recover; + + let signature = [self.r().to_be_bytes(), self.s().to_be_bytes()].concat(); + let public_key = secp256k1_recover(&self.signed_hash(), self.recovery_id(), &signature)?; + + let Hash(address) = hash(&public_key.to_bytes()); + let address: [u8; 20] = address[12..32].try_into()?; + + Ok(Address::from(address)) + } + + #[must_use] + pub fn nonce(&self) -> u64 { + match self.transaction { + TransactionPayload::Legacy(LegacyTx { nonce, .. }) + | TransactionPayload::AccessList(AccessListTx { nonce, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { nonce, .. }) => nonce, } - }; + } - let hash = solana_program::keccak::hashv(&[&header, middle, &trailer]).to_bytes(); + #[must_use] + pub fn gas_price(&self) -> U256 { + match self.transaction { + TransactionPayload::Legacy(LegacyTx { gas_price, .. }) + | TransactionPayload::AccessList(AccessListTx { gas_price, .. }) => gas_price, + TransactionPayload::DynamicFee(DynamicFeeTx { + max_priority_fee_per_gas, + max_fee_per_gas, + .. + }) => { + // Metamask case. + // Currently, the Metamask does not use native RPC methods for gas estimation and + // sets max_priority_fee_per_gas = max_fee_per_gas for DynamicGas transactions + // when it can't estimate the gas price. + // For such a case, we will treat DynamicGas transactions as legacy ones: + // - gas_price is equal to max_fee_per_gas, + // - we do not charge the Priority Fee from the User (gas is charged as for Legacy txn). + if max_fee_per_gas == max_priority_fee_per_gas { + max_fee_per_gas + } else { + // return base_fee_per_gas as a gas_price - priority fee is charged per iteration separately. + max_fee_per_gas - max_priority_fee_per_gas + } + } + } + } - Ok(hash) + #[must_use] + pub fn gas_limit(&self) -> U256 { + match self.transaction { + TransactionPayload::Legacy(LegacyTx { gas_limit, .. }) + | TransactionPayload::AccessList(AccessListTx { gas_limit, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { gas_limit, .. }) => gas_limit, + } + } + + pub fn gas_limit_in_tokens(&self) -> Result { + self.gas_price() + .checked_mul(self.gas_limit()) + .ok_or(Error::IntegerOverflow) + } + + pub fn priority_fee_limit_in_tokens(&self) -> Result { + self.max_priority_fee_per_gas() + .unwrap_or_default() + .checked_mul(self.gas_limit()) + .ok_or(Error::IntegerOverflow) + } + + #[must_use] + pub fn target(&self) -> Option
{ + match self.transaction { + TransactionPayload::Legacy(LegacyTx { target, .. }) + | TransactionPayload::AccessList(AccessListTx { target, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { target, .. }) => target, + } + } + + #[must_use] + pub fn value(&self) -> U256 { + match self.transaction { + TransactionPayload::Legacy(LegacyTx { value, .. }) + | TransactionPayload::AccessList(AccessListTx { value, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { value, .. }) => value, + } + } + + #[must_use] + pub fn call_data(&self) -> &[u8] { + match &self.transaction { + TransactionPayload::Legacy(LegacyTx { call_data, .. }) + | TransactionPayload::AccessList(AccessListTx { call_data, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { call_data, .. }) => call_data, + } + } + + #[must_use] + pub fn r(&self) -> U256 { + match self.transaction { + TransactionPayload::Legacy(LegacyTx { r, .. }) + | TransactionPayload::AccessList(AccessListTx { r, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { r, .. }) => r, + } + } + + #[must_use] + pub fn s(&self) -> U256 { + match self.transaction { + TransactionPayload::Legacy(LegacyTx { s, .. }) + | TransactionPayload::AccessList(AccessListTx { s, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { s, .. }) => s, + } + } + + #[must_use] + pub fn chain_id(&self) -> Option { + match self.transaction { + TransactionPayload::Legacy(LegacyTx { chain_id, .. }) => chain_id, + TransactionPayload::AccessList(AccessListTx { chain_id, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { chain_id, .. }) => Some(chain_id), + } + .map(std::convert::TryInto::try_into) + .transpose() + .expect("chain_id < u64::max") + } + + #[must_use] + pub fn recovery_id(&self) -> u8 { + match self.transaction { + TransactionPayload::Legacy(LegacyTx { recovery_id, .. }) + | TransactionPayload::AccessList(AccessListTx { recovery_id, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { recovery_id, .. }) => recovery_id, + } + } + + #[must_use] + pub fn rlp_len(&self) -> usize { + self.byte_len + } + + #[must_use] + pub fn hash(&self) -> [u8; 32] { + self.hash + } + + #[must_use] + pub fn signed_hash(&self) -> [u8; 32] { + self.signed_hash + } + + #[must_use] + pub fn tx_type(&self) -> u8 { + match self.transaction { + TransactionPayload::Legacy(_) => 0, + TransactionPayload::AccessList(_) => 1, + TransactionPayload::DynamicFee(_) => 2, + } + } + + #[must_use] + pub fn max_fee_per_gas(&self) -> Option { + match self.transaction { + TransactionPayload::Legacy(_) | TransactionPayload::AccessList(_) => None, + TransactionPayload::DynamicFee(DynamicFeeTx { + max_fee_per_gas, .. + }) => Some(max_fee_per_gas), + } + } + + #[must_use] + pub fn max_priority_fee_per_gas(&self) -> Option { + match self.transaction { + TransactionPayload::Legacy(_) | TransactionPayload::AccessList(_) => None, + TransactionPayload::DynamicFee(DynamicFeeTx { + max_priority_fee_per_gas, + .. + }) => Some(max_priority_fee_per_gas), + } + } + + #[must_use] + pub fn access_list(&self) -> Option<&Vector<(Address, Vector)>> { + match &self.transaction { + TransactionPayload::AccessList(AccessListTx { access_list, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { access_list, .. }) => Some(access_list), + TransactionPayload::Legacy(_) => None, + } + } + + pub fn use_gas_limit_multiplier(&mut self) { + let gas_multiplier = U256::from(GAS_LIMIT_MULTIPLIER_NO_CHAINID); + + match &mut self.transaction { + TransactionPayload::AccessList(AccessListTx { gas_limit, .. }) + | TransactionPayload::DynamicFee(DynamicFeeTx { gas_limit, .. }) + | TransactionPayload::Legacy(LegacyTx { gas_limit, .. }) => { + *gas_limit = gas_limit.saturating_mul(gas_multiplier); + } + } + } + + #[maybe_async] + pub async fn validate( + &self, + origin: Address, + backend: &impl AccountStorage, + ) -> Result<(), crate::error::Error> { + let chain_id = self + .chain_id() + .unwrap_or_else(|| backend.default_chain_id()); + + if !backend.is_valid_chain_id(chain_id) { + return Err(Error::InvalidChainId(chain_id)); + } + + let origin_nonce = backend.nonce(origin, chain_id).await; + if origin_nonce != self.nonce() { + let error = Error::InvalidTransactionNonce(origin, origin_nonce, self.nonce()); + return Err(error); + } + + // The reason to forbid the calls for DynamicFee transactions - priority fee calculation + // uses get_processed_sibling_instruction syscall which doesn't work well for CPI. + if self.tx_type() == 2 && get_stack_height() != TRANSACTION_LEVEL_STACK_HEIGHT { + return Err(Error::Custom( + "CPI calls of Neon EVM are forbidden for DynamicFee transaction type.".to_owned(), + )); + } + + Ok(()) + } } #[inline] diff --git a/evm_loader/program/src/types/tree_map.rs b/evm_loader/program/src/types/tree_map.rs new file mode 100644 index 000000000..847a4e82c --- /dev/null +++ b/evm_loader/program/src/types/tree_map.rs @@ -0,0 +1,187 @@ +use std::{ + cmp::Ordering, + fmt::{self, Debug, Display}, + hash::Hash, + ops::Index, + usize, +}; + +use super::Vector; +use crate::allocator::acc_allocator; + +#[derive(Clone)] +#[repr(C)] +pub struct TreeMap { + entries: Vector<(K, V)>, +} + +impl TreeMap { + #[must_use] + pub fn new() -> Self { + TreeMap { + entries: Vector::new_in(acc_allocator()), + } + } + + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + TreeMap { + entries: Vector::with_capacity_in(capacity, acc_allocator()), + } + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + #[must_use] + pub fn len(&self) -> usize { + self.entries.len() + } + + pub fn get(&self, key: &K) -> Option<&V> { + self.entries + .binary_search_by_key(key, |&(k, _)| k) + .map_or(Option::None, |idx| Option::Some(&self.entries[idx].1)) + } + + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.entries + .binary_search_by_key(key, |(k, _)| *k) + .map_or(Option::None, |idx| Option::Some(&mut self.entries[idx].1)) + } + + pub fn insert(&mut self, key: K, value: V) -> Option + where + V: Clone, + { + match self.entries.binary_search_by_key(&key, |(k, _)| *k) { + Ok(idx) => { + // Clone is better in performance than potential vec realloc. + let old = self.entries[idx].1.clone(); + self.entries[idx] = (key, value); + Some(old) + } + Err(idx) => { + self.entries.insert(idx, (key, value)); + None + } + } + } + + pub fn update_or_insert(&mut self, key: K, value: &V, f: F) -> Result<(), E> + where + F: FnOnce(V) -> Result, + V: Clone, + { + match self.entries.binary_search_by_key(&key, |(k, _)| *k) { + Ok(idx) => { + let entry = &self.entries[idx]; + self.entries[idx] = (key, f(entry.1.clone())?); + } + Err(idx) => { + self.entries.insert(idx, (key, value.clone())); + } + } + Ok(()) + } + + pub fn remove(&mut self, key: &K) -> Option { + match self.entries.binary_search_by_key(key, |(k, _)| *k) { + Ok(idx) => { + let entry = self.entries.remove(idx); + Some(entry.1) + } + Err(_) => None, + } + } + + pub fn remove_entry(&mut self, key: &K) -> Option<(K, V)> { + match self.entries.binary_search_by_key(key, |(k, _)| *k) { + Ok(idx) => Some(self.entries.remove(idx)), + Err(_) => None, + } + } + + pub fn keys(&self) -> impl Iterator { + self.entries.iter().map(|(k, _)| k) + } +} + +impl Default for TreeMap { + fn default() -> Self { + Self::new() + } +} + +impl Index for TreeMap { + type Output = V; + + fn index(&self, index: K) -> &Self::Output { + self.get(&index).expect("no entry found for key") + } +} + +impl FromIterator<(K, V)> for TreeMap { + fn from_iter>(iter: T) -> Self { + let mut last_key: Option = None; + let mut entries = Vector::new_in(acc_allocator()); + + for item in iter { + let prev_key = last_key.replace(item.0); + if let Some(ref prev) = prev_key { + match Ord::cmp(prev, &item.0) { + Ordering::Less => (), + // Insert the last one from the consequtive list of equal keys. + Ordering::Equal => continue, + // Panic as we expect to have a valid iterator with non-decreasing keys. + Ordering::Greater => panic!("map keys should be non-decreasing"), + } + } + entries.push(item); + } + + TreeMap { entries } + } +} + +#[allow(clippy::iter_without_into_iter)] +impl<'a, K: 'a, V: 'a> TreeMap { + pub fn iter(&'a self) -> std::slice::Iter<'a, (K, V)> { + self.entries.iter() + } + + pub fn iter_mut(&'a mut self) -> std::slice::IterMut<'a, (K, V)> { + self.entries.iter_mut() + } +} + +impl fmt::Debug for TreeMap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut res = write!(f, "TreeMap {{"); + for i in 0..self.entries.len() { + let e = &self.entries[i]; + res = res.and(write!(f, "{:?} -> {:?}, ", e.0, e.1)); + } + res.and(write!(f, " }}")) + } +} + +impl fmt::Display for TreeMap { + // This trait requires `fmt` with this exact signature. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut res = write!(f, "TreeMap {{"); + for i in 0..self.entries.len() { + let e = &self.entries[i]; + res = res.and(write!(f, "{} -> {}, ", e.0, e.1)); + } + res.and(write!(f, " }}")) + } +} + +impl Hash for TreeMap { + fn hash(&self, state: &mut H) { + self.entries.hash(state); + } +} diff --git a/evm_loader/program/src/types/vector.rs b/evm_loader/program/src/types/vector.rs new file mode 100644 index 000000000..e76ff7400 --- /dev/null +++ b/evm_loader/program/src/types/vector.rs @@ -0,0 +1,94 @@ +use crate::allocator::{acc_allocator, StateAccountAllocator}; +use allocator_api2::SliceExt; + +pub type Vector = allocator_api2::vec::Vec; + +#[macro_export] +macro_rules! vector { + () => ( + allocator_api2::vec::Vec::new_in($crate::allocator::acc_allocator()) + ); + ($elem:expr; $n:expr) => ( + allocator_api2::vec::from_elem_in($elem, $n, $crate::allocator::acc_allocator()) + ); + ($($x:expr),+ $(,)?) => ( + allocator_api2::boxed::Box::<[_], $crate::allocator::StateAccountAllocator>::into_vec( + allocator_api2::boxed::Box::slice( + allocator_api2::boxed::Box::new_in([$($x),+], $crate::allocator::acc_allocator()) + ) + ) + ); +} + +pub trait VectorVecExt { + fn into_vector(self) -> Vector + where + T: Copy + Default; +} + +pub trait VectorSliceExt { + fn to_vector(&self) -> Vector + where + T: Copy + Default; +} + +pub trait VectorVecSlowExt { + fn elementwise_copy_into_vector(self) -> Vector + where + T: Clone; +} + +pub trait VectorSliceSlowExt { + fn elementwise_copy_to_vector(&self) -> Vector + where + T: Clone; +} + +impl VectorVecExt for Vec { + fn into_vector(self) -> Vector { + let mut ret = Vector::with_capacity_in(self.len(), crate::allocator::acc_allocator()); + // SAFETY: + // allocated above with the capacity of `self.len()`, and initialize to `self.len()` in + // ptr::copy_to_non_overlapping below. + unsafe { + self.as_ptr() + .copy_to_nonoverlapping(ret.as_mut_ptr(), self.len()); + ret.set_len(self.len()); + } + ret + } +} + +impl VectorSliceExt for [T] { + fn to_vector(&self) -> Vector { + let mut ret = Vector::with_capacity_in(self.len(), crate::allocator::acc_allocator()); + // SAFETY: + // allocated above with the capacity of `self.len()`, and initialize to `self.len()` in + // ptr::copy_to_non_overlapping below. + unsafe { + self.as_ptr() + .copy_to_nonoverlapping(ret.as_mut_ptr(), self.len()); + ret.set_len(self.len()); + } + ret + } +} + +impl VectorSliceSlowExt for [T] { + fn elementwise_copy_to_vector(&self) -> Vector + where + T: Clone, + { + SliceExt::to_vec_in(self, acc_allocator()) + } +} + +impl VectorVecSlowExt for Vec { + fn elementwise_copy_into_vector(self) -> Vector { + let mut ret = Vector::with_capacity_in(self.len(), acc_allocator()); + for item in self { + ret.push(item); + } + ret + } +} diff --git a/evm_loader/rpc-client/Cargo.toml b/evm_loader/rpc-client/Cargo.toml new file mode 100644 index 000000000..d010a2216 --- /dev/null +++ b/evm_loader/rpc-client/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "neon-rpc-client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = "1.0.204" +serde_json = "1.0.121" +neon-lib = { path = "../lib" } +thiserror = "1.0.63" +async-trait = "0.1.81" +jsonrpsee-core = "0.22.5" +jsonrpsee-http-client = "0.22.5" +jsonrpsee-types = "0.22.5" +tokio = { version = "1", features = ["full"] } +build-info = "0.0.37" + +[build-dependencies] +build-info-build = "0.0.37" diff --git a/evm_loader/rpc-client/src/config.rs b/evm_loader/rpc-client/src/config.rs new file mode 100644 index 000000000..f26026b01 --- /dev/null +++ b/evm_loader/rpc-client/src/config.rs @@ -0,0 +1,9 @@ +pub struct NeonRpcClientConfig { + pub url: String, +} + +impl NeonRpcClientConfig { + pub fn new(url: impl Into) -> Self { + Self { url: url.into() } + } +} diff --git a/evm_loader/rpc-client/src/error.rs b/evm_loader/rpc-client/src/error.rs new file mode 100644 index 000000000..9fa8c2f2e --- /dev/null +++ b/evm_loader/rpc-client/src/error.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NeonRpcClientError { + #[error("Jsonrpc error. {0:?}")] + JsonrpseeError(#[from] jsonrpsee_core::client::Error), + #[error("serde json error. {0:?}")] + SerdeJsonError(#[from] serde_json::Error), +} diff --git a/evm_loader/rpc-client/src/http.rs b/evm_loader/rpc-client/src/http.rs new file mode 100644 index 000000000..d81134619 --- /dev/null +++ b/evm_loader/rpc-client/src/http.rs @@ -0,0 +1,113 @@ +#![allow(clippy::future_not_send)] + +use async_trait::async_trait; +use jsonrpsee_core::{client::ClientT, rpc_params}; +use jsonrpsee_http_client::{HttpClient, HttpClientBuilder}; +use neon_lib::LibMethod; +use neon_lib::{ + commands::{ + emulate::EmulateResponse, get_balance::GetBalanceResponse, get_config::GetConfigResponse, + get_contract::GetContractResponse, get_holder::GetHolderResponse, + get_storage_at::GetStorageAtReturn, + }, + types::{ + EmulateApiRequest, GetBalanceRequest, GetContractRequest, GetHolderRequest, + GetStorageAtRequest, + }, +}; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::{config::NeonRpcClientConfig, NeonRpcClient, NeonRpcClientResult}; + +pub struct NeonRpcHttpClient { + client: HttpClient, +} + +impl NeonRpcHttpClient { + pub fn new(config: NeonRpcClientConfig) -> NeonRpcClientResult { + Ok(Self { + client: HttpClientBuilder::default().build(config.url)?, + }) + } +} + +pub struct NeonRpcHttpClientBuilder {} + +impl NeonRpcHttpClientBuilder { + #[must_use] + pub const fn new() -> Self { + Self {} + } + + pub fn build(&self, url: impl Into) -> NeonRpcClientResult { + let config = NeonRpcClientConfig::new(url); + NeonRpcHttpClient::new(config) + } +} + +impl Default for NeonRpcHttpClientBuilder { + fn default() -> Self { + Self::new() + } +} + +#[async_trait(?Send)] +impl NeonRpcClient for NeonRpcHttpClient { + async fn emulate(&self, params: EmulateApiRequest) -> NeonRpcClientResult { + self.request(LibMethod::Emulate, params).await + } + + async fn balance( + &self, + params: GetBalanceRequest, + ) -> NeonRpcClientResult> { + self.request(LibMethod::GetBalance, params).await + } + + async fn get_contract( + &self, + params: GetContractRequest, + ) -> NeonRpcClientResult> { + self.request(LibMethod::GetContract, params).await + } + + async fn get_config(&self) -> NeonRpcClientResult { + self.request_without_params(LibMethod::GetConfig).await + } + + async fn get_holder(&self, params: GetHolderRequest) -> NeonRpcClientResult { + self.request(LibMethod::GetHolder, params).await + } + + async fn get_storage_at( + &self, + params: GetStorageAtRequest, + ) -> NeonRpcClientResult { + self.request(LibMethod::GetStorageAt, params).await + } + + async fn trace(&self, params: EmulateApiRequest) -> NeonRpcClientResult { + self.request(LibMethod::Trace, params).await + } +} + +impl NeonRpcHttpClient { + async fn request(&self, method: LibMethod, params: P) -> NeonRpcClientResult + where + P: Serialize, + R: DeserializeOwned, + { + Ok(self + .client + .request(method.into(), rpc_params![params]) + .await?) + } + + async fn request_without_params(&self, method: LibMethod) -> NeonRpcClientResult + where + R: DeserializeOwned, + { + Ok(self.client.request(method.into(), rpc_params![]).await?) + } +} diff --git a/evm_loader/rpc-client/src/lib.rs b/evm_loader/rpc-client/src/lib.rs new file mode 100644 index 000000000..36233df2f --- /dev/null +++ b/evm_loader/rpc-client/src/lib.rs @@ -0,0 +1,44 @@ +#![deny(warnings)] +#![deny(clippy::all, clippy::pedantic, clippy::nursery)] +#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)] + +mod config; +mod error; +pub mod http; + +pub use error::NeonRpcClientError; + +use async_trait::async_trait; +use neon_lib::{ + commands::{ + emulate::EmulateResponse, get_balance::GetBalanceResponse, get_config::GetConfigResponse, + get_contract::GetContractResponse, get_holder::GetHolderResponse, + get_storage_at::GetStorageAtReturn, + }, + types::{ + EmulateApiRequest, GetBalanceRequest, GetContractRequest, GetHolderRequest, + GetStorageAtRequest, + }, +}; + +type NeonRpcClientResult = Result; + +#[async_trait(?Send)] +pub trait NeonRpcClient { + async fn emulate(&self, params: EmulateApiRequest) -> NeonRpcClientResult; + async fn balance( + &self, + params: GetBalanceRequest, + ) -> NeonRpcClientResult>; + async fn get_contract( + &self, + params: GetContractRequest, + ) -> NeonRpcClientResult>; + async fn get_holder(&self, params: GetHolderRequest) -> NeonRpcClientResult; + async fn get_config(&self) -> NeonRpcClientResult; + async fn get_storage_at( + &self, + params: GetStorageAtRequest, + ) -> NeonRpcClientResult; + async fn trace(&self, params: EmulateApiRequest) -> NeonRpcClientResult; +} diff --git a/evm_loader/rpc/.gitignore b/evm_loader/rpc/.gitignore new file mode 100644 index 000000000..e82451797 --- /dev/null +++ b/evm_loader/rpc/.gitignore @@ -0,0 +1,3 @@ +libs +d +keys \ No newline at end of file diff --git a/evm_loader/rpc/Cargo.toml b/evm_loader/rpc/Cargo.toml new file mode 100644 index 000000000..0274a6c6d --- /dev/null +++ b/evm_loader/rpc/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "neon-rpc" +version = "1.15.0-dev" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = "4.8.0" +clap = "2.34.0" +jsonrpc-v2 = "0.13.0" +neon-lib-interface = { path = "../lib-interface" } +neon-lib = { path = "../lib" } +semver = "1.0.23" +serde = "1.0.204" +serde_json = "1.0.121" +tokio = { version = "1", features = ["full"] } +build-info = "0.0.37" +thiserror = "1.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-appender = "0.2.3" + +[build-dependencies] +build-info-build = "0.0.37" diff --git a/evm_loader/rpc/build.rs b/evm_loader/rpc/build.rs new file mode 100644 index 000000000..d36778f60 --- /dev/null +++ b/evm_loader/rpc/build.rs @@ -0,0 +1,3 @@ +fn main() { + build_info_build::build_script(); +} diff --git a/evm_loader/rpc/src/build_info.rs b/evm_loader/rpc/src/build_info.rs new file mode 100644 index 000000000..85f42da0d --- /dev/null +++ b/evm_loader/rpc/src/build_info.rs @@ -0,0 +1,7 @@ +use neon_lib::build_info_common::SlimBuildInfo; + +build_info::build_info!(fn build_info); + +pub fn get_build_info() -> SlimBuildInfo { + build_info().into() +} diff --git a/evm_loader/rpc/src/context.rs b/evm_loader/rpc/src/context.rs new file mode 100644 index 000000000..614b629a5 --- /dev/null +++ b/evm_loader/rpc/src/context.rs @@ -0,0 +1,6 @@ +use neon_lib_interface::NeonEVMLib_Ref; +use std::collections::HashMap; + +pub struct Context { + pub libraries: HashMap, +} diff --git a/evm_loader/rpc/src/error.rs b/evm_loader/rpc/src/error.rs new file mode 100644 index 000000000..18119963f --- /dev/null +++ b/evm_loader/rpc/src/error.rs @@ -0,0 +1,27 @@ +use neon_lib::errors::NeonError; +use neon_lib_interface::NeonEVMLibLoadError; +use std::net::AddrParseError; + +use thiserror::Error; + +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Error)] +pub enum NeonRPCError { + /// Std IO Error + #[error("Std I/O error. {0:?}")] + StdIoError(#[from] std::io::Error), + #[error("Addr parse error. {0:?}")] + AddrParseError(#[from] AddrParseError), + #[error("Neon error. {0:?}")] + NeonError(#[from] NeonError), + #[error("Neon lib error. {0:?}")] + NeonEVMLibLoadError(#[from] NeonEVMLibLoadError), + #[error("Neon RPC: Incorrect parameters.")] + IncorrectParameters(), +} + +impl From for jsonrpc_v2::Error { + fn from(value: NeonRPCError) -> Self { + Self::internal(value) + } +} diff --git a/evm_loader/rpc/src/handlers/emulate.rs b/evm_loader/rpc/src/handlers/emulate.rs new file mode 100644 index 000000000..b169683b0 --- /dev/null +++ b/evm_loader/rpc/src/handlers/emulate.rs @@ -0,0 +1,19 @@ +#![allow(clippy::future_not_send)] + +use super::invoke; +use crate::{context::Context, error::NeonRPCError}; +use jsonrpc_v2::{Data, Params}; +use neon_lib::{types::EmulateApiRequest, LibMethod}; + +pub async fn handle( + ctx: Data, + Params(params): Params>, +) -> Result { + let param = params.first().ok_or(NeonRPCError::IncorrectParameters())?; + invoke( + LibMethod::Emulate, + ctx, + Some(serde_json::value::to_value(param).unwrap()), + ) + .await +} diff --git a/evm_loader/rpc/src/handlers/get_balance.rs b/evm_loader/rpc/src/handlers/get_balance.rs new file mode 100644 index 000000000..5ecaefd9f --- /dev/null +++ b/evm_loader/rpc/src/handlers/get_balance.rs @@ -0,0 +1,19 @@ +#![allow(clippy::future_not_send)] + +use super::invoke; +use crate::{context::Context, error::NeonRPCError}; +use jsonrpc_v2::{Data, Params}; +use neon_lib::{types::GetBalanceRequest, LibMethod}; + +pub async fn handle( + ctx: Data, + Params(params): Params>, +) -> Result { + let param = params.first().ok_or(NeonRPCError::IncorrectParameters())?; + invoke( + LibMethod::GetBalance, + ctx, + Some(serde_json::value::to_value(param).unwrap()), + ) + .await +} diff --git a/evm_loader/rpc/src/handlers/get_config.rs b/evm_loader/rpc/src/handlers/get_config.rs new file mode 100644 index 000000000..b1f1f4c74 --- /dev/null +++ b/evm_loader/rpc/src/handlers/get_config.rs @@ -0,0 +1,10 @@ +#![allow(clippy::future_not_send)] + +use super::invoke; +use crate::context::Context; +use jsonrpc_v2::Data; +use neon_lib::LibMethod; + +pub async fn handle(ctx: Data) -> Result { + invoke(LibMethod::GetConfig, ctx, Option::::None).await +} diff --git a/evm_loader/rpc/src/handlers/get_contract.rs b/evm_loader/rpc/src/handlers/get_contract.rs new file mode 100644 index 000000000..094246593 --- /dev/null +++ b/evm_loader/rpc/src/handlers/get_contract.rs @@ -0,0 +1,19 @@ +#![allow(clippy::future_not_send)] + +use super::invoke; +use crate::{context::Context, error::NeonRPCError}; +use jsonrpc_v2::{Data, Params}; +use neon_lib::{types::GetContractRequest, LibMethod}; + +pub async fn handle( + ctx: Data, + Params(params): Params>, +) -> Result { + let param = params.first().ok_or(NeonRPCError::IncorrectParameters())?; + invoke( + LibMethod::GetContract, + ctx, + Some(serde_json::value::to_value(param).unwrap()), + ) + .await +} diff --git a/evm_loader/rpc/src/handlers/get_holder.rs b/evm_loader/rpc/src/handlers/get_holder.rs new file mode 100644 index 000000000..2c8efa2fc --- /dev/null +++ b/evm_loader/rpc/src/handlers/get_holder.rs @@ -0,0 +1,19 @@ +#![allow(clippy::future_not_send)] + +use super::invoke; +use crate::{context::Context, error::NeonRPCError}; +use jsonrpc_v2::{Data, Params}; +use neon_lib::{types::GetHolderRequest, LibMethod}; + +pub async fn handle( + ctx: Data, + Params(params): Params>, +) -> Result { + let param = params.first().ok_or(NeonRPCError::IncorrectParameters())?; + invoke( + LibMethod::GetHolder, + ctx, + Some(serde_json::value::to_value(param).unwrap()), + ) + .await +} diff --git a/evm_loader/rpc/src/handlers/get_storage_at.rs b/evm_loader/rpc/src/handlers/get_storage_at.rs new file mode 100644 index 000000000..2e265e7e7 --- /dev/null +++ b/evm_loader/rpc/src/handlers/get_storage_at.rs @@ -0,0 +1,19 @@ +#![allow(clippy::future_not_send)] + +use super::invoke; +use crate::{context::Context, error::NeonRPCError}; +use jsonrpc_v2::{Data, Params}; +use neon_lib::{types::GetStorageAtRequest, LibMethod}; + +pub async fn handle( + ctx: Data, + Params(params): Params>, +) -> Result { + let param = params.first().ok_or(NeonRPCError::IncorrectParameters())?; + invoke( + LibMethod::GetStorageAt, + ctx, + Some(serde_json::value::to_value(param).unwrap()), + ) + .await +} diff --git a/evm_loader/rpc/src/handlers/info.rs b/evm_loader/rpc/src/handlers/info.rs new file mode 100644 index 000000000..afc36bb76 --- /dev/null +++ b/evm_loader/rpc/src/handlers/info.rs @@ -0,0 +1,7 @@ +use serde_json::json; + +use crate::build_info::get_build_info; + +pub async fn handle() -> Result { + Ok(json!(get_build_info())) +} diff --git a/evm_loader/rpc/src/handlers/lib_info.rs b/evm_loader/rpc/src/handlers/lib_info.rs new file mode 100644 index 000000000..1d0f80bdb --- /dev/null +++ b/evm_loader/rpc/src/handlers/lib_info.rs @@ -0,0 +1,7 @@ +use super::lib_build_info; +use crate::context::Context; +use jsonrpc_v2::Data; + +pub async fn handle(ctx: Data) -> Result { + lib_build_info(ctx).await +} diff --git a/evm_loader/rpc/src/handlers/mod.rs b/evm_loader/rpc/src/handlers/mod.rs new file mode 100644 index 000000000..ea541301a --- /dev/null +++ b/evm_loader/rpc/src/handlers/mod.rs @@ -0,0 +1,81 @@ +#![allow(clippy::future_not_send)] + +pub mod emulate; +pub mod get_balance; +pub mod get_config; +pub mod get_contract; +pub mod get_holder; +pub mod get_storage_at; +pub mod info; +pub mod lib_info; +pub mod trace; + +use crate::context::Context; +use jsonrpc_v2::Data; +use neon_lib::LibMethod; +use neon_lib_interface::{types::NeonEVMLibError, NeonEVMLib_Ref}; +use serde::Serialize; +use serde_json::Value; + +fn get_library(context: &Data) -> Result<&NeonEVMLib_Ref, jsonrpc_v2::Error> { + // just for testing + let hash = context + .libraries + .keys() + .last() + .ok_or_else(|| jsonrpc_v2::Error::internal("library collection is empty")); + let has_ref = &hash?.clone(); + let library = context.libraries.get(has_ref).ok_or_else(|| { + jsonrpc_v2::Error::internal(format!("Library not found for hash {has_ref:?}")) + })?; + + tracing::debug!("ver {:?}", library.hash()()); + + Ok(library) +} + +pub async fn invoke( + method: LibMethod, + context: Data, + params: Option, +) -> Result { + let library = get_library(&context)?; + + let method_str: &str = method.into(); + let mut params_str: String = String::new(); + if let Some(params_value) = params { + params_str = serde_json::to_string(¶ms_value).unwrap(); + } + + library.invoke()(method_str.into(), params_str.as_str().into()) + .await + .map(|x| serde_json::from_str::(&x).unwrap()) + .map_err(|s| { + let NeonEVMLibError { + code, + message, + data, + } = serde_json::from_str(s.as_str()).unwrap(); + + jsonrpc_v2::Error::Full { + code: i64::from(code), + message, + data: Some(Box::new( + data.as_ref() + .and_then(Value::as_str) + .unwrap_or("null") + .to_string(), + )), + } + }) + .into() +} + +pub async fn lib_build_info( + context: Data, +) -> Result { + let library = get_library(&context)?; + let build_info = library.get_build_info()(); + + Ok(serde_json::from_str::(&build_info).unwrap()) +} diff --git a/evm_loader/rpc/src/handlers/trace.rs b/evm_loader/rpc/src/handlers/trace.rs new file mode 100644 index 000000000..dd38ffaa5 --- /dev/null +++ b/evm_loader/rpc/src/handlers/trace.rs @@ -0,0 +1,19 @@ +#![allow(clippy::future_not_send)] + +use super::invoke; +use crate::{context::Context, error::NeonRPCError}; +use jsonrpc_v2::{Data, Params}; +use neon_lib::{types::EmulateApiRequest, LibMethod}; + +pub async fn handle( + ctx: Data, + Params(params): Params>, +) -> Result { + let param = params.first().ok_or(NeonRPCError::IncorrectParameters())?; + invoke( + LibMethod::Trace, + ctx, + Some(serde_json::value::to_value(param).unwrap()), + ) + .await +} diff --git a/evm_loader/rpc/src/main.rs b/evm_loader/rpc/src/main.rs new file mode 100644 index 000000000..921947704 --- /dev/null +++ b/evm_loader/rpc/src/main.rs @@ -0,0 +1,81 @@ +#![deny(warnings)] +#![deny(clippy::all, clippy::pedantic, clippy::nursery)] +#![allow(clippy::module_name_repetitions)] + +// use std::{collections::HashMap, error::Error}; +mod build_info; +mod context; +mod error; +mod handlers; +mod options; +mod rpc; + +use crate::build_info::get_build_info; +use context::Context; +use error::NeonRPCError; +use neon_lib::config; +use std::{env, net::SocketAddr, str::FromStr}; +use tracing::info; +use tracing_appender::non_blocking::NonBlockingBuilder; + +type NeonRPCResult = Result; + +#[actix_web::main] +async fn main() -> NeonRPCResult<()> { + let matches = options::parse(); + + // initialize tracing + let (non_blocking, _guard) = NonBlockingBuilder::default() + .lossy(false) + .finish(std::io::stdout()); + + tracing_subscriber::fmt().with_writer(non_blocking).init(); + + let lib_dir = matches.value_of("LIB-DIR").unwrap(); + let libraries = neon_lib_interface::load_libraries(lib_dir)?; + + info!("BUILD INFO: {}", get_build_info()); + info!( + "LIBRARY DIR: {}, count: {}", + lib_dir, + libraries.keys().len(), + ); + + if libraries.keys().len() > 0 { + info!("=== LIBRARY VERSIONS: ================================================================="); + for library_ver in libraries.keys() { + info!("Lib version: {}", library_ver); + } + info!("=== END LIBRARY VERSIONS =============================================================="); + } + + // check configs + let _api_config = config::load_api_config_from_environment(); + + let ctx = Context { libraries }; + let rpc = rpc::build_rpc(ctx); + + let listener_addr = matches + .value_of("host") + .map(std::borrow::ToOwned::to_owned) + .or_else(|| { + Some(env::var("NEON_API_LISTENER_ADDR").unwrap_or_else(|_| "0.0.0.0:3100".to_owned())) + }) + .unwrap(); + + let addr = SocketAddr::from_str(listener_addr.as_str())?; + + actix_web::HttpServer::new(move || { + let rpc = rpc.clone(); + actix_web::App::new().service( + actix_web::web::service("/") + .guard(actix_web::guard::Post()) + .finish(rpc.into_web_service()), + ) + }) + .bind(addr)? + .run() + .await?; + + Ok(()) +} diff --git a/evm_loader/rpc/src/options.rs b/evm_loader/rpc/src/options.rs new file mode 100644 index 000000000..c7d953ce4 --- /dev/null +++ b/evm_loader/rpc/src/options.rs @@ -0,0 +1,26 @@ +use clap::ArgMatches; + +pub fn parse<'a>() -> ArgMatches<'a> { + clap::App::new("Neon Core RPC") + .version(env!("CARGO_PKG_VERSION")) + .author("Neon Labs") + .about("Runs a Neon Core RPC server") + .arg( + clap::Arg::with_name("LIB-DIR") + .env("NEON_LIB_DIR") + .alias("dir") + .help("Directory with neon libraries to load") + .required(true) + .index(1), + ) + .arg( + clap::Arg::with_name("HOST") + .alias("host") + .env("NEON_API_LISTENER_ADDR") + .default_value("0.0.0.0:3100") + .help("RPC host to connect to") + .required(false) + .index(2), + ) + .get_matches() +} diff --git a/evm_loader/rpc/src/rpc.rs b/evm_loader/rpc/src/rpc.rs new file mode 100644 index 000000000..eb49a5042 --- /dev/null +++ b/evm_loader/rpc/src/rpc.rs @@ -0,0 +1,24 @@ +use crate::context::Context; +use crate::handlers::{ + emulate, get_balance, get_config, get_contract, get_holder, get_storage_at, info, lib_info, + trace, +}; + +use jsonrpc_v2::{Data, MapRouter, Server}; +use neon_lib::LibMethod; +use std::sync::Arc; + +pub fn build_rpc(ctx: Context) -> Arc> { + Server::new() + .with_data(Data::new(ctx)) + .with_method("build_info", info::handle) + .with_method("lib_build_info", lib_info::handle) + .with_method(LibMethod::GetStorageAt.to_string(), get_storage_at::handle) + .with_method(LibMethod::Trace.to_string(), trace::handle) + .with_method(LibMethod::Emulate.to_string(), emulate::handle) + .with_method(LibMethod::GetBalance.to_string(), get_balance::handle) + .with_method(LibMethod::GetConfig.to_string(), get_config::handle) + .with_method(LibMethod::GetHolder.to_string(), get_holder::handle) + .with_method(LibMethod::GetContract.to_string(), get_contract::handle) + .finish() +} diff --git a/evm_loader/solana-run-neon.sh b/evm_loader/solana-run-neon.sh deleted file mode 100755 index e6365379a..000000000 --- a/evm_loader/solana-run-neon.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash - -set -e - -SOLANA_BIN=/opt/solana/bin -NEON_BIN=/opt - -EVM_LOADER_SO=evm_loader.so -EVM_LOADER=$(${SOLANA_BIN}/solana address -k ${NEON_BIN}/evm_loader-keypair.json) -EVM_LOADER_PATH=${NEON_BIN}/${EVM_LOADER_SO} - -function initialize_neon() { - # deploy tokens needed by Neon EVM - # temporary disable load NeonEVM in genesis - #export SKIP_EVM_DEPLOY=${DEPLOY_EVM_IN_GENESIS:-NO} - export SKIP_EVM_DEPLOY=NO - export SOLANA_URL=http://127.0.0.1:8899 - export EVM_LOADER - - cd ${NEON_BIN} - ./wait-for-solana.sh ${SOLANA_WAIT_TIMEOUT:-60} - ./deploy-evm.sh -} - -initialize_neon & - -# run Solana with Neon EVM in genesis -cd ${SOLANA_BIN} -cp ${EVM_LOADER_PATH} . - -# dump metaplex program from mainnet -METAPLEX=metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s -METAPLEX_SO=metaplex.so - -if [[ "${DEPLOY_EVM_IN_GENESIS:-YES}" == "YES" ]]; then -# temporary disable load NeoneEVM in genesis -# NEON_BPF_ARGS=( -# --bpf-program ${EVM_LOADER} BPFLoader2111111111111111111111111111111111 ${EVM_LOADER_SO} -# --bpf-program ${METAPLEX} BPFLoader2111111111111111111111111111111111 ${METAPLEX_SO} -# ) - NEON_BPF_ARGS=( - --bpf-program ${METAPLEX} BPFLoader2111111111111111111111111111111111 ${METAPLEX_SO} - ) -fi - -NEON_VALIDATOR_ARGS=( - --gossip-host $(hostname -i) - --log-messages-bytes-limit 50000 -) - -if [[ -n $GEYSER_PLUGIN_CONFIG ]]; then - echo "Using geyser plugin with config: $GEYSER_PLUGIN_CONFIG" - NEON_VALIDATOR_ARGS+=(--geyser-plugin-config $GEYSER_PLUGIN_CONFIG) -fi - -export SOLANA_RUN_SH_GENESIS_ARGS="${NEON_BPF_ARGS[@]}" -export SOLANA_RUN_SH_VALIDATOR_ARGS="${NEON_VALIDATOR_ARGS[@]}" - -./solana-run.sh diff --git a/evm_loader/tests/.gitignore b/evm_loader/tests/.gitignore deleted file mode 100644 index fc4b05c7d..000000000 --- a/evm_loader/tests/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -contracts/*.binary -__pycache__ -.pytest_cache \ No newline at end of file diff --git a/evm_loader/tests/README.md b/evm_loader/tests/README.md deleted file mode 100644 index 95e372390..000000000 --- a/evm_loader/tests/README.md +++ /dev/null @@ -1,105 +0,0 @@ -Intro -====== - -In this directory are located tests for the evm which sends transactions direct to the evm. -Tests are using python and py.test library for run tests. - - -Installation ------------- - -```bash -pip install -r requirements.txt -py.test ./ -s -v -``` - -Moreover, we can use additional command line keys: - -1. --operator-keys - path to 2 comma separated operator keys (by default ~/.config/solana/id.json,~/.config/solana/id2.json) - -Also we can configure some variables from environment variables: - -1. SOLANA_URL - by default http://localhost:8899 -2. EVM_LOADER - set evm loader address -3. NEON_TOKEN_MINT - ethereum token mint address - - -How to write tests -================== - -Structure ---------- - -Test has a several helper directories and files: - -1. contracts - place for all Solidity contracts used in tests -2. utils - place for utilities which divided by logic parts -3. conftest.py - common fixtures for all tests - - -Common fixtures ---------------- - -For more information about fixtures see [pytest-fixture](https://docs.pytest.org/en/latest/fixture.html). -In several words, fixtures are functions which are called before and after each test and has a scope parameter which setup how often this function will be called. -For example: - -```python -import pytest - -@pytest.fixture(scope="function") -def fixture1(): - print("Call fixture1") - return "fixture1" - - -def test_one(fixture1): - pass - - -def test_two(fixture1): - pass -``` - -In this case "fixture1" will be called before each test. - -```python -import pytest - -@pytest.fixture(scope="session") -def fixture1(): - print("Call fixture1") - return "fixture1" - - -def test_one(fixture1): - pass - - -def test_two(fixture1): - pass -``` - -In this case "fixture1" will be called only once before all tests. - -We have a several common fixtures: - -1. evm_loader - fixture for evm loader object -2. operator_keypair - solana Keypair for operator key -3. treasury_pool - created treasury pool -4. user_account - created user account with ethereum account - - -Tips -==== - -Generate eth contract function call data ---------------------------------------- - -```python -from eth_utils import abi -func_name = abi.function_signature_to_4byte_selector('unchange_storage(uint8,uint8)') -data = (func_name + bytes.fromhex("%064x" % 0x01) + bytes.fromhex("%064x" % 0x01)) -``` - -uint8 parameters must be 64 bytes long diff --git a/evm_loader/tests/__init__.py b/evm_loader/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/evm_loader/tests/conftest.py b/evm_loader/tests/conftest.py deleted file mode 100644 index 1fcab2bd7..000000000 --- a/evm_loader/tests/conftest.py +++ /dev/null @@ -1,133 +0,0 @@ -import os -import json -import pathlib - -import eth_abi -import pytest - -from solana.keypair import Keypair -from eth_keys import keys as eth_keys -from solana.publickey import PublicKey -from solana.rpc.commitment import Confirmed - -from .solana_utils import EvmLoader, OperatorAccount, create_treasury_pool_address, make_new_user, get_solana_balance, \ - deposit_neon, solana_client -from .utils.contract import deploy_contract -from .utils.storage import create_holder -from .utils.types import TreasuryPool, Caller, Contract - - -def pytest_addoption(parser): - parser.addoption( - "--operator-keys", action="store", default="~/.config/solana/id.json,~/.config/solana/id2.json", - help="Path to 2 comma separated operator keypairs" - ) - - -def pytest_configure(config): - if "RUST_LOG" in os.environ: - pytest.CONTRACTS_PATH = pathlib.Path("/opt/solidity") - else: - pytest.CONTRACTS_PATH = pathlib.Path(__file__).parent / "contracts" - - -@pytest.fixture(scope="session") -def evm_loader(request) -> EvmLoader: - wallet = OperatorAccount( - pathlib.Path(request.config.getoption("--operator-keys").split(',')[0]).expanduser().as_posix()) - loader = EvmLoader(wallet) - return loader - - -@pytest.fixture(scope="session") -def operator_keypair(request, evm_loader) -> Keypair: - """ - Initialized solana keypair with balance. Get private key from cli or ~/.config/solana/id.json - """ - with open(pathlib.Path(request.config.getoption("--operator-keys").split(',')[0]).expanduser(), "r") as key: - secret_key = json.load(key)[:32] - account = Keypair.from_secret_key(secret_key) - caller_ether = eth_keys.PrivateKey(account.secret_key[:32]).public_key.to_canonical_address() - caller, caller_nonce = evm_loader.ether2program(caller_ether) - - if get_solana_balance(PublicKey(caller)) == 0: - evm_loader.check_account(account.public_key) - evm_loader.check_account(PublicKey(caller)) - evm_loader.create_ether_account(caller_ether) - return account - - -@pytest.fixture(scope="session") -def second_operator_keypair(request, evm_loader) -> Keypair: - """ - Initialized solana keypair with balance. Get private key from cli or ~/.config/solana/id.json - """ - with open(pathlib.Path(request.config.getoption("--operator-keys").split(",")[1]).expanduser(), "r") as key: - secret_key = json.load(key)[:32] - account = Keypair.from_secret_key(secret_key) - caller_ether = eth_keys.PrivateKey(account.secret_key[:32]).public_key.to_canonical_address() - caller, caller_nonce = evm_loader.ether2program(caller_ether) - - if get_solana_balance(PublicKey(caller)) == 0: - evm_loader.create_ether_account(caller_ether) - return account - - -@pytest.fixture(scope="session") -def treasury_pool(evm_loader) -> TreasuryPool: - index = 2 - address = create_treasury_pool_address(index) - index_buf = index.to_bytes(4, 'little') - return TreasuryPool(index, address, index_buf) - - -@pytest.fixture(scope="function") -def user_account(evm_loader) -> Caller: - return make_new_user(evm_loader) - - -@pytest.fixture(scope="session") -def session_user(evm_loader) -> Caller: - return make_new_user(evm_loader) - - -@pytest.fixture(scope="session") -def second_session_user(evm_loader) -> Caller: - return make_new_user(evm_loader) - - -@pytest.fixture(scope="session") -def sender_with_tokens(evm_loader, operator_keypair) -> Caller: - user = make_new_user(evm_loader) - deposit_neon(evm_loader, operator_keypair, user.eth_address, 100000) - return user - - -@pytest.fixture(scope="session") -def holder_acc(operator_keypair) -> PublicKey: - return create_holder(operator_keypair) - - -@pytest.fixture(scope="function") -def new_holder_acc(operator_keypair) -> PublicKey: - return create_holder(operator_keypair) - - -@pytest.fixture(scope="function") -def rw_lock_contract(evm_loader: EvmLoader, operator_keypair: Keypair, session_user: Caller, - treasury_pool) -> Contract: - return deploy_contract(operator_keypair, session_user, "rw_lock.binary", evm_loader, treasury_pool) - - -@pytest.fixture(scope="function") -def rw_lock_caller(evm_loader: EvmLoader, operator_keypair: Keypair, - session_user: Caller, treasury_pool: TreasuryPool, rw_lock_contract: Contract) -> Contract: - constructor_args = eth_abi.encode(['address'], [rw_lock_contract.eth_address.hex()]) - return deploy_contract(operator_keypair, session_user, "rw_lock_caller.binary", evm_loader, - treasury_pool, encoded_args=constructor_args) - - -@pytest.fixture(scope="function") -def string_setter_contract(evm_loader: EvmLoader, operator_keypair: Keypair, session_user: Caller, - treasury_pool) -> Contract: - return deploy_contract(operator_keypair, session_user, "string_setter.binary", evm_loader, treasury_pool) diff --git a/evm_loader/tests/contracts/contracts.sol b/evm_loader/tests/contracts/contracts.sol deleted file mode 100644 index b51e10c60..000000000 --- a/evm_loader/tests/contracts/contracts.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.5.12; - -contract rw_lock { - mapping(address => mapping(uint256 => uint256)) public data; - uint len = 0; - string public text; - - function unchange_storage(uint8 x, uint8 y) public pure returns(uint8) { - return x + y; - } - - function update_storage(uint resize) public { - uint n = 0; - - while (n < resize){ - data[msg.sender][len+n] = uint256(len+n); - n = n + 1; - } - len = len + resize; - } - - function update_storage_str(string memory new_text) public { - text = new_text; - } - - function update_storage_map(uint resize) public { - uint n = 0; - while (n < resize){ - data[msg.sender][n] = uint256(n); - n = n + 1; - } - } - - function get_text() public view returns (string memory) { - return text; - } - - function deploy_contract() public returns(address){ - hello_world hello = new hello_world(); - hello.call_hello_world(); - return address(hello); - } - -} - - -contract hello_world { - uint public num = 5; - string public text = "Hello World!"; - - function call_hello_world() public view returns (string memory) { - return text; - } -} - -contract small { - function call_hello() public view returns (string memory) { - return "Hi"; - } -} - - -contract string_setter{ - string public text; - - - function get() public view returns (string memory) { - return text; - } - - function set(string memory new_text) public payable { - text = new_text; - } - - -} - -contract rw_lock_caller { - rw_lock rw; - - constructor(address rw_lock_address) { - rw = rw_lock(rw_lock_address); - } - - function unchange_storage(uint8 x, uint8 y) public view returns(uint8) { - return rw.unchange_storage(x, y); - } - - function update_storage_str(string memory new_text) public { - rw.update_storage_str(new_text); - } - - function update_storage_map(uint resize) public { - rw.update_storage_map(resize); - } - - function get_text() public view returns (string memory) { - return rw.get_text(); - } -} - diff --git a/evm_loader/tests/eth_tx_utils.py b/evm_loader/tests/eth_tx_utils.py deleted file mode 100644 index e5ec11fb7..000000000 --- a/evm_loader/tests/eth_tx_utils.py +++ /dev/null @@ -1,228 +0,0 @@ -from sha3 import keccak_256 -import json -from web3.auto import w3 -from eth_keys import keys -import struct - - -def unpack(data): - ch = data[0] - if ch <= 0x7F: - return ch, data[1:] - elif ch == 0x80: - return None, data[1:] - elif ch <= 0xB7: - l = ch - 0x80 - return data[1:1 + l].tobytes(), data[1 + l:] - elif ch <= 0xBF: - lLen = ch - 0xB7 - l = int.from_bytes(data[1:1 + lLen], byteorder='big') - return data[1 + lLen:1 + lLen + l].tobytes(), data[1 + lLen + l:] - elif ch == 0xC0: - return (), data[1:] - elif ch <= 0xF7: - l = ch - 0xC0 - lst = list() - sub = data[1:1 + l] - while len(sub): - (item, sub) = unpack(sub) - lst.append(item) - return lst, data[1 + l:] - else: - lLen = ch - 0xF7 - l = int.from_bytes(data[1:1 + lLen], byteorder='big') - lst = list() - sub = data[1 + lLen:1 + lLen + l] - while len(sub): - (item, sub) = unpack(sub) - lst.append(item) - return lst, data[1 + lLen + l:] - - -def pack(data): - if data is None: - return (0x80).to_bytes(1, 'big') - if isinstance(data, str): - return pack(data.encode('utf8')) - elif isinstance(data, bytes): - if len(data) <= 55: - return (len(data) + 0x80).to_bytes(1, 'big') + data - else: - l = len(data) - lLen = (l.bit_length() + 7) // 8 - return (0xB7 + lLen).to_bytes(1, 'big') + l.to_bytes(lLen, 'big') + data - elif isinstance(data, int): - if data < 0x80: - return data.to_bytes(1, 'big') - else: - l = (data.bit_length() + 7) // 8 - return (l + 0x80).to_bytes(1, 'big') + data.to_bytes(l, 'big') - pass - elif isinstance(data, list) or isinstance(data, tuple): - if len(data) == 0: - return (0xC0).to_bytes(1, 'big') - else: - res = bytearray() - for d in data: - res += pack(d) - l = len(res) - if l <= 55: - return (l + 0xC0).to_bytes(1, 'big') + res - else: - lLen = (l.bit_length() + 7) // 8 - return (lLen + 0xF7).to_bytes(1, 'big') + l.to_bytes(lLen, 'big') + res - else: - raise Exception("Unknown type {} of data".format(str(type(data)))) - - -def get_int(a): - if isinstance(a, int): - return a - if isinstance(a, bytes): - return int.from_bytes(a, 'big') - if a is None: - return a - raise Exception("Invalid convertion from {} to int".format(a)) - - -class Trx: - def __init__(self): - self.nonce = None - self.gasPrice = None - self.gasLimit = None - self.toAddress = None - self.value = None - self.callData = None - self.v = None - self.r = None - self.s = None - - @classmethod - def from_string(cls, s): - t = Trx() - (unpacked, data) = unpack(memoryview(s)) - (nonce, gasPrice, gasLimit, toAddress, value, callData, v, r, s) = unpacked - t.nonce = get_int(nonce) - t.gasPrice = get_int(gasPrice) - t.gasLimit = get_int(gasLimit) - t.toAddress = toAddress - t.value = get_int(value) - t.callData = callData - t.v = get_int(v) - t.r = get_int(r) - t.s = get_int(s) - return t - - def chain_id(self): - # chainid*2 + 35 xxxxx0 + 100011 xxxx0 + 100010 +1 - # chainid*2 + 36 xxxxx0 + 100100 xxxx0 + 100011 +1 - return (self.v - 1) // 2 - 17 - - def __str__(self): - return pack(( - self.nonce, - self.gasPrice, - self.gasLimit, - self.toAddress, - self.value, - self.callData, - self.v, - self.r.to_bytes(32, 'big') if self.r else None, - self.s.to_bytes(32, 'big') if self.s else None) - ).hex() - - def get_msg(self, chain_id=None): - return pack(( - self.nonce, - self.gasPrice, - self.gasLimit, - self.toAddress, - self.value, - self.callData, - chain_id or self.chain_id(), None, None)) - - def hash(self, chain_id=None): - trx = pack(( - self.nonce, - self.gasPrice, - self.gasLimit, - self.toAddress, - self.value, - self.callData, - chain_id or self.chain_id(), None, None)) - return keccak_256(trx).digest() - - def sender(self): - msg_hash = self.hash() - sig = keys.Signature(vrs=[1 if self.v % 2 == 0 else 0, self.r, self.s]) - pub = sig.recover_public_key_from_msg_hash(msg_hash) - return pub.to_canonical_address().hex() - - -class JsonEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, bytes): - return obj.hex() - return json.JSONEncoder.default(obj) - - -def make_instruction_data_from_tx(instruction, private_key=None): - if isinstance(instruction, dict): - if instruction['chainId'] is None: - raise Exception("chainId value is needed in input dict") - if private_key is None: - raise Exception("Needed private key for transaction creation from fields") - - signed_tx = w3.eth.account.sign_transaction(instruction, private_key) - # print(signed_tx.rawTransaction.hex()) - _trx = Trx.from_string(signed_tx.rawTransaction) - # print(json.dumps(_trx.__dict__, cls=JsonEncoder, indent=3)) - - raw_msg = _trx.get_msg(instruction['chainId']) - sig = keys.Signature(vrs=[1 if _trx.v % 2 == 0 else 0, _trx.r, _trx.s]) - pub = sig.recover_public_key_from_msg_hash(_trx.hash()) - - # print(pub.to_hex()) - - return pub.to_canonical_address(), sig.to_bytes(), raw_msg - elif isinstance(instruction, str): - if instruction[:2] == "0x": - instruction = instruction[2:] - - _trx = Trx.from_string(bytearray.fromhex(instruction)) - # print(json.dumps(_trx.__dict__, cls=JsonEncoder, indent=3)) - - raw_msg = _trx.get_msg() - sig = keys.Signature(vrs=[1 if _trx.v % 2 == 0 else 0, _trx.r, _trx.s]) - pub = sig.recover_public_key_from_msg_hash(_trx.hash()) - - data = pub.to_canonical_address() - data += sig.to_bytes() - data += raw_msg - - return pub.to_canonical_address(), sig.to_bytes(), raw_msg - else: - raise Exception("function gets ") - - -def make_keccak_instruction_data(check_instruction_index, msg_len, data_start): - if 255 < check_instruction_index < 0: - raise Exception("Invalid index for instruction - {}".format(check_instruction_index)) - - check_count = 1 - eth_address_size = 20 - signature_size = 65 - eth_address_offset = data_start - signature_offset = eth_address_offset + eth_address_size - message_data_offset = signature_offset + signature_size - - data = struct.pack("B", check_count) - data += struct.pack("= confirmations: - return - sleep_time = 1 - time.sleep(sleep_time) - elapsed_time += sleep_time - raise RuntimeError("could not confirm transaction: ", tx_sig) - - -def account_with_seed(base, seed, program) -> PublicKey: - return PublicKey(sha256(bytes(base) + bytes(seed, 'utf8') + bytes(program)).digest()) - - -def create_account_with_seed(funding, base, seed, lamports, space, program=PublicKey(EVM_LOADER)): - created = account_with_seed(base, seed, program) - print(f"Created: {created}") - return sp.create_account_with_seed(sp.CreateAccountWithSeedParams( - from_pubkey=funding, - new_account_pubkey=created, - base_pubkey=base, - seed=seed, - lamports=lamports, - space=space, - program_id=program - )) - - -def create_holder_account(account, operator): - return TransactionInstruction( - keys=[ - AccountMeta(pubkey=account, is_signer=False, is_writable=True), - AccountMeta(pubkey=operator, is_signer=True, is_writable=False), - ], - program_id=PublicKey(EVM_LOADER), - data=bytes.fromhex("24") - ) - - -class solana_cli: - def __init__(self, acc=None): - self.acc = acc - - def call(self, arguments): - if self.acc is None: - cmd = '{} --url {} {}'.format(path_to_solana, SOLANA_URL, arguments) - else: - cmd = '{} --keypair {} --url {} {}'.format(path_to_solana, self.acc.get_path(), SOLANA_URL, arguments) - try: - return subprocess.check_output(cmd, shell=True, universal_newlines=True) - except subprocess.CalledProcessError as err: - print(f"ERR: solana error {err}") - raise - - -class neon_cli: - def __init__(self, verbose_flags=''): - self.verbose_flags = verbose_flags - - def call(self, arguments): - cmd = 'neon-cli {} --commitment=processed --url {} {} -vvv'.format(self.verbose_flags, SOLANA_URL, arguments) - proc_result = subprocess.run(cmd, shell=True, text=True, stdout=subprocess.PIPE, universal_newlines=True) - result = json.loads(proc_result.stdout) - if result["result"] == "error": - error = result["error"] - raise Exception(f"ERR: neon-cli error {error}") - - proc_result.check_returncode() - return result["value"] - - def emulate(self, loader_id, sender, contract, data): - cmd = ["neon-cli", - "--commitment=recent", - "--url", SOLANA_URL, - f"--evm_loader={loader_id}", - "emulate", - sender, - contract - ] - print('cmd:', cmd) - print("data:", data) - - data_json = "" - if data: - data_json = f'{{"data": "0x{data}"}}' - - proc_result = subprocess.run(cmd, input=data_json, text=True, stdout=subprocess.PIPE, universal_newlines=True) - - result = json.loads(proc_result.stdout) - if result["result"] == "error": - error = result["error"] - raise Exception(f"ERR: neon-cli error {error}") - - proc_result.check_returncode() - return result["value"] - - def call_contract_get_function(self, evm_loader, sender, contract, function_signature: str, constructor_args=None): - data = abi.function_signature_to_4byte_selector(function_signature) - if constructor_args is not None: - data += constructor_args - result = self.emulate(evm_loader.loader_id, sender.eth_address.hex(), contract.eth_address.hex(), data.hex()) - return result["result"] - - def get_steps_count(self, evm_loader, from_acc, to, data): - if isinstance(to, (Caller, Contract)): - to = to.eth_address.hex() - - result = neon_cli().emulate( - evm_loader.loader_id, - from_acc.eth_address.hex(), - to, - data - ) - - return result["steps_executed"] - - -class RandomAccount: - def __init__(self, path=None): - if path is None: - self.make_random_path() - print(f"New keypair file: {self.path}") - self.generate_key() - else: - self.path = path - self.retrieve_keys() - print('New Public key:', self.acc.public_key()) - print('Private:', self.acc.secret_key()) - - def make_random_path(self): - self.path = os.urandom(5).hex() + ".json" - - def generate_key(self): - cmd_generate = 'solana-keygen new --no-passphrase --outfile {}'.format(self.path) - try: - return subprocess.check_output(cmd_generate, shell=True, universal_newlines=True) - except subprocess.CalledProcessError as err: - print(f"ERR: solana error {err}") - raise - - def retrieve_keys(self): - with open(self.path) as f: - d = json.load(f) - self.acc = Keypair(d[0:32]) - - def get_path(self): - return self.path - - def get_acc(self): - return self.acc - - -class WalletAccount(RandomAccount): - def __init__(self, path): - self.path = path - self.retrieve_keys() - print('Wallet public key:', self.acc.public_key()) - - -class OperatorAccount: - def __init__(self, path=None): - if path is None: - self.path = pathlib.Path.home() / ".config" / "solana" / "id.json" - else: - self.path = path - self.retrieve_keys() - - def retrieve_keys(self): - with open(self.path) as f: - d = json.load(f) - self.acc = Keypair.from_secret_key(d[0:32]) - - def get_path(self): - return self.path - - def get_acc(self) -> Keypair: - return self.acc - - -class EvmLoader: - def __init__(self, acc: OperatorAccount, program_id=EVM_LOADER): - if program_id is None: - print(f"EVM Loader program address is empty, deploy it") - result = json.loads(solana_cli(acc).call('deploy {}'.format(EVM_LOADER_SO))) - program_id = result['programId'] - EvmLoader.loader_id = PublicKey(program_id) - print("Done\n") - - self.loader_id = EvmLoader.loader_id - self.acc = acc - print("Evm loader program: {}".format(self.loader_id)) - - def deploy(self, contract_path, config=None): - print(f'Deploy contract from path: {contract_path}') - if config is None: - output = neon_cli().call("deploy --evm_loader {} {}".format(self.loader_id, contract_path)) - else: - output = neon_cli().call("deploy --evm_loader {} --config {} {}".format(self.loader_id, config, - contract_path)) - print(f"Deploy output: {output}") - result = json.loads(output.splitlines()[-1]) - return result - - def create_ether_account(self, ether): - (trx, sol) = self.create_ether_account_trx(ether) - signer = self.acc.get_acc() - send_transaction(solana_client, trx, signer) - return sol - - @staticmethod - def ether2hex(ether: Union[str, bytes]): - if isinstance(ether, str): - if ether.startswith('0x'): - return ether[2:] - return ether - return ether.hex() - - @staticmethod - def ether2bytes(ether: Union[str, bytes]): - if isinstance(ether, str): - if ether.startswith('0x'): - return bytes.fromhex(ether[2:]) - return bytes.fromhex(ether) - return ether - - def ether2seed(self, ether: Union[str, bytes]): - seed = b58encode(ACCOUNT_SEED_VERSION + self.ether2bytes(ether)).decode('utf8') - acc = account_with_seed(self.acc.get_acc().public_key, seed, self.loader_id) - print('ether2program: {} {} => {}'.format(self.ether2hex(ether), 255, acc)) - return acc, 255 - - def ether2program(self, ether: Union[str, bytes]) -> Tuple[str, int]: - items = PublicKey.find_program_address([ACCOUNT_SEED_VERSION, self.ether2bytes(ether)], PublicKey(EVM_LOADER)) - return str(items[0]), items[1] - - def check_account(self, solana): - info = solana_client.get_account_info(solana) - print("checkAccount({}): {}".format(solana, info)) - - def deploy_checked(self, location, caller, caller_ether): - trx_count = get_transaction_count(solana_client, caller) - ether = keccak_256(rlp.encode((caller_ether, trx_count))).digest()[-20:] - - program = self.ether2program(ether) - info = solana_client.get_account_info(PublicKey(program[0])) - if info.value is None: - res = self.deploy(location) - return res['programId'], bytes.fromhex(res['ethereum'][2:]) - elif info.value.owner != self.loader_id: - raise Exception("Invalid owner for account {}".format(program)) - else: - return program[0], ether - - def create_ether_account_trx(self, ether: Union[str, bytes]) -> Tuple[Transaction, str]: - (sol, nonce) = self.ether2program(ether) - print('createEtherAccount: {} {} => {}'.format(ether, nonce, sol)) - base = self.acc.get_acc().public_key - data = bytes.fromhex('28') + CREATE_ACCOUNT_LAYOUT.build(dict(ether=self.ether2bytes(ether))) - trx = Transaction() - trx.add(TransactionInstruction( - program_id=self.loader_id, - data=data, - keys=[ - AccountMeta(pubkey=base, is_signer=True, is_writable=True), - AccountMeta(pubkey=PublicKey(SYSTEM_ADDRESS), is_signer=False, is_writable=False), - AccountMeta(pubkey=PublicKey(sol), is_signer=False, is_writable=True), - ])) - return trx, sol - - -def get_solana_balance(account): - return solana_client.get_balance(account, commitment=Confirmed).value - - -class AccountInfo(NamedTuple): - ether: eth_keys.PublicKey - trx_count: int - - @staticmethod - def from_bytes(data: bytes): - cont = ACCOUNT_INFO_LAYOUT.parse(data) - return AccountInfo(cont.ether, cont.trx_count) - - -def get_account_data(solana_client: Client, account: Union[str, PublicKey, Keypair], expected_length: int) -> bytes: - if isinstance(account, Keypair): - account = account.public_key - print(f"Get account data for {account}") - info = solana_client.get_account_info(account, commitment=Confirmed) - print(f"Result: {info}") - info = info.value - if info is None: - raise Exception("Can't get information about {}".format(account)) - if len(info.data) < expected_length: - print("len(data)({}) < expected_length({})".format(len(info.data), expected_length)) - raise Exception("Wrong data length for account data {}".format(account)) - return info.data - - -def get_transaction_count(solana_client: Client, sol_account: Union[str, PublicKey]) -> int: - info = get_account_data(solana_client, sol_account, ACCOUNT_INFO_LAYOUT.sizeof()) - acc_info = AccountInfo.from_bytes(info) - res = int.from_bytes(acc_info.trx_count, 'little') - print('getTransactionCount {}: {}'.format(sol_account, res)) - return res - - -def get_neon_balance(solana_client: Client, sol_account: Union[str, PublicKey]) -> int: - info = get_account_data(solana_client, sol_account, ACCOUNT_INFO_LAYOUT.sizeof()) - account = ACCOUNT_INFO_LAYOUT.parse(info) - balance = int.from_bytes(account.balance, byteorder="little") - print('getNeonBalance {}: {}'.format(sol_account, balance)) - return balance - - -def send_transaction(client: Client, trx, acc, wait_status=Confirmed): - print("Send trx") - result = client.send_transaction(trx, acc, opts=TxOpts(skip_confirmation=True, preflight_commitment=wait_status)) - tx = result.value - print("Result: {}".format(result)) - wait_confirm_transaction(client, tx) - for _ in range(60): - receipt = client.confirm_transaction(tx) - if receipt.value is not None: - break - time.sleep(1) - else: - raise AssertionError(f"Can't get confirmed transaction ") - return solana_client.get_transaction(tx) - - -def evm_step_cost(): - operator_expences = PAYMENT_TO_TREASURE + LAMPORTS_PER_SIGNATURE - return math.floor(operator_expences / EVM_STEPS) - - -def make_new_user(evm_loader: EvmLoader): - key = Keypair.generate() - if get_solana_balance(key.public_key) == 0: - tx = solana_client.request_airdrop(key.public_key, 1000000 * 10 ** 9, commitment=Confirmed) - wait_confirm_transaction(solana_client, tx.value) - caller_ether = eth_keys.PrivateKey(key.secret_key[:32]).public_key.to_canonical_address() - caller, caller_nonce = evm_loader.ether2program(caller_ether) - caller_token = get_associated_token_address(PublicKey(caller), NEON_TOKEN_MINT_ID) - - if get_solana_balance(PublicKey(caller)) == 0: - print(f"Create Neon account {caller_ether} for user {caller}") - evm_loader.create_ether_account(caller_ether) - - print('Account solana address:', key.public_key) - print(f'Account ether address: {caller_ether.hex()} {caller_nonce}', ) - print(f'Account solana address: {caller}') - return Caller(key, PublicKey(caller), caller_ether, caller_nonce, caller_token) - - -def deposit_neon(evm_loader: EvmLoader, operator_keypair: Keypair, ether_address: Union[str, bytes], amount: int): - ether_pubkey, _ether_bump_seed = evm_loader.ether2program(ether_address) - - evm_token_authority, _auth_bump_seed = \ - PublicKey.find_program_address([bytes("Deposit", encoding='utf-8')], evm_loader.loader_id) - evm_pool_key = get_associated_token_address(evm_token_authority, NEON_TOKEN_MINT_ID) - - signer_token_pubkey = get_associated_token_address(operator_keypair.public_key, NEON_TOKEN_MINT_ID) - trx = Transaction() - trx.add( - spl.token.instructions.approve( - ApproveParams( - spl.token.constants.TOKEN_PROGRAM_ID, - signer_token_pubkey, - PublicKey(ether_pubkey), - operator_keypair.public_key, - amount, - ) - ) - ) - trx.add( - make_DepositV03( - evm_loader.ether2bytes(ether_address), - PublicKey(ether_pubkey), - signer_token_pubkey, - evm_pool_key, - spl.token.constants.TOKEN_PROGRAM_ID, - operator_keypair.public_key, - ) - ) - - receipt = send_transaction(solana_client, trx, operator_keypair) - - return receipt - - -def cancel_transaction( - tx_hash: HexBytes, - holder_acc: PublicKey, - operator_keypair: Keypair, - additional_accounts: typing.List[PublicKey], -): - # Cancel deployment transaction: - trx = Transaction() - trx.add( - make_Cancel( - holder_acc, - operator_keypair, - tx_hash, - additional_accounts, - ) - ) - - cancel_receipt = send_transaction(solana_client, trx, operator_keypair) - - print("Cancel receipt:", cancel_receipt) - assert cancel_receipt.value.transaction.meta.err is None - return cancel_receipt - - -def write_transaction_to_holder_account( - signed_tx: SignedTransaction, - holder_account: PublicKey, - operator: Keypair, -): - # Write transaction to transaction holder account - offset = 0 - receipts = [] - rest = signed_tx.rawTransaction - while len(rest): - (part, rest) = (rest[:920], rest[920:]) - trx = Transaction() - trx.add(make_WriteHolder(operator.public_key, holder_account, signed_tx.hash, offset, part)) - receipts.append( - solana_client.send_transaction( - trx, - operator, - opts=TxOpts(skip_confirmation=True, preflight_commitment=Confirmed), - ) - ) - offset += len(part) - for rcpt in receipts: - wait_confirm_transaction(solana_client, rcpt.value) - - -def execute_trx_from_instruction(operator: Keypair, evm_loader, treasury_address: PublicKey, treasury_buffer: bytes, - instruction: SignedTransaction, - additional_accounts, signer: Keypair, system_program=sp.SYS_PROGRAM_ID, - evm_loader_public_key=PublicKey(EVM_LOADER)) -> SendTransactionResp: - trx = TransactionWithComputeBudget(operator) - trx.add(make_ExecuteTrxFromInstruction(operator, evm_loader, treasury_address, - treasury_buffer, instruction.rawTransaction, additional_accounts, - system_program, evm_loader_public_key)) - - return solana_client.send_transaction(trx, signer, opts=TxOpts(skip_preflight=False, skip_confirmation=False)) - - -def send_transaction_step_from_instruction(operator: Keypair, evm_loader, treasury, storage_account, - instruction: SignedTransaction, - additional_accounts, steps_count, signer: Keypair, - system_program=sp.SYS_PROGRAM_ID, - evm_loader_public_key=PublicKey(EVM_LOADER)) -> SendTransactionResp: - trx = TransactionWithComputeBudget(operator) - trx.add( - make_PartialCallOrContinueFromRawEthereumTX( - instruction.rawTransaction, - operator, evm_loader, storage_account, treasury.account, treasury.buffer, steps_count, - additional_accounts, system_program, evm_loader_public_key - ) - ) - - return solana_client.send_transaction(trx, signer, opts=TxOpts(skip_preflight=False, skip_confirmation=False)) - - -def execute_transaction_steps_from_instruction(operator: Keypair, evm_loader, treasury, storage_account, - instruction: SignedTransaction, - additional_accounts, steps_count=EVM_STEPS, - signer: Keypair = None) -> SendTransactionResp: - signer = operator if signer is None else signer - - send_transaction_step_from_instruction(operator, evm_loader, treasury, storage_account, instruction, - additional_accounts, 1, signer) - if steps_count > 0: - steps_left = steps_count - while steps_left > 0: - send_transaction_step_from_instruction(operator, evm_loader, treasury, storage_account, instruction, - additional_accounts, EVM_STEPS, signer) - steps_left = steps_left - EVM_STEPS - return send_transaction_step_from_instruction(operator, evm_loader, treasury, storage_account, instruction, - additional_accounts, 1, signer) - - -def send_transaction_step_from_account(operator: Keypair, evm_loader, treasury, storage_account, - additional_accounts, steps_count, signer: Keypair, - system_program=sp.SYS_PROGRAM_ID, - evm_loader_public_key=PublicKey(EVM_LOADER), tag=33) -> GetTransactionResp: - trx = TransactionWithComputeBudget(operator) - trx.add( - make_ExecuteTrxFromAccountDataIterativeOrContinue( - operator, evm_loader, storage_account, treasury.account, treasury.buffer, steps_count, - additional_accounts, system_program, evm_loader_public_key, tag - ) - ) - return send_transaction(solana_client, trx, signer) - - -def execute_transaction_steps_from_account(operator: Keypair, evm_loader, treasury, storage_account, - additional_accounts, steps_count=EVM_STEPS, - signer: Keypair = None) -> GetTransactionResp: - signer = operator if signer is None else signer - - send_transaction_step_from_account(operator, evm_loader, treasury, storage_account, additional_accounts, 1, signer) - if steps_count > 0: - steps_left = steps_count - while steps_left > 0: - send_transaction_step_from_account(operator, evm_loader, treasury, storage_account, additional_accounts, - EVM_STEPS, signer) - steps_left = steps_left - EVM_STEPS - return send_transaction_step_from_account(operator, evm_loader, treasury, storage_account, additional_accounts, 1, - signer) - - -def execute_transaction_steps_from_account_no_chain_id(operator: Keypair, evm_loader, treasury, storage_account, - additional_accounts, steps_count=EVM_STEPS, - signer: Keypair = None) -> GetTransactionResp: - signer = operator if signer is None else signer - - send_transaction_step_from_account(operator, evm_loader, treasury, storage_account, additional_accounts, 1, signer, - tag=34) - if steps_count > 0: - steps_left = steps_count - while steps_left > 0: - send_transaction_step_from_account(operator, evm_loader, treasury, storage_account, additional_accounts, - EVM_STEPS, signer, tag=34) - steps_left = steps_left - EVM_STEPS - return send_transaction_step_from_account(operator, evm_loader, treasury, storage_account, additional_accounts, 1, - signer, tag=34) diff --git a/evm_loader/tests/test_cancel_trx.py b/evm_loader/tests/test_cancel_trx.py deleted file mode 100644 index 3356a10d2..000000000 --- a/evm_loader/tests/test_cancel_trx.py +++ /dev/null @@ -1,42 +0,0 @@ -from solana.transaction import Transaction - -from .solana_utils import send_transaction, solana_client, get_transaction_count, \ - send_transaction_step_from_instruction -from .utils.constants import TAG_FINALIZED_STATE, TAG_STATE -from .utils.contract import make_contract_call_trx -from .utils.storage import create_holder -from .utils.instructions import make_Cancel -from .utils.layouts import FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, STORAGE_ACCOUNT_INFO_LAYOUT -from .utils.transaction_checks import check_holder_account_tag - - -# We need test here two types of transaction -class TestCancelTrx: - - def test_cancel_trx(self, operator_keypair, rw_lock_contract, user_account, treasury_pool, evm_loader): - """EVM can cancel transaction and finalize storage account""" - signed_tx = make_contract_call_trx(user_account, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) - - storage_account = create_holder(operator_keypair) - trx = send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, storage_account, - signed_tx, - [rw_lock_contract.solana_address, - user_account.solana_account_address], 1, operator_keypair) - - receipt = solana_client.get_transaction(trx.value) - assert receipt.value.transaction.meta.err is None - check_holder_account_tag(storage_account, STORAGE_ACCOUNT_INFO_LAYOUT, TAG_STATE) - - user_nonce = get_transaction_count(solana_client, user_account.solana_account_address) - trx = Transaction() - trx.add( - make_Cancel(storage_account, operator_keypair, signed_tx.hash, - [ - rw_lock_contract.solana_address, - user_account.solana_account_address, - ] - ) - ) - send_transaction(solana_client, trx, operator_keypair) - check_holder_account_tag(storage_account, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - assert user_nonce < get_transaction_count(solana_client, user_account.solana_account_address) diff --git a/evm_loader/tests/test_cli.py b/evm_loader/tests/test_cli.py deleted file mode 100644 index a5887bf76..000000000 --- a/evm_loader/tests/test_cli.py +++ /dev/null @@ -1,174 +0,0 @@ -import os -import random - -import pytest -from solana.rpc.api import Client -from solana.publickey import PublicKey -from solana.rpc.commitment import Confirmed - -from .solana_utils import neon_cli, create_treasury_pool_address, get_neon_balance, get_transaction_count -from .solana_utils import solana_client, wait_confirm_transaction, get_solana_balance, send_transaction -from .utils.constants import SOLANA_URL -from .utils.contract import deploy_contract -from .utils.ethereum import make_eth_transaction -from eth_utils import abi, to_text - -from .utils.instructions import TransactionWithComputeBudget, make_PartialCallOrContinueFromRawEthereumTX -from .utils.storage import create_holder - - -def gen_hash_of_block(size: int) -> str: - """Generates a block hash of the given size""" - try: - block_hash = hex(int.from_bytes(os.urandom(size), "big")) - if bytes.fromhex(block_hash[2:]) or len(block_hash[2:]) != size * 2: - return block_hash - except ValueError: - return gen_hash_of_block(size) - - -def test_emulate_transfer(user_account, evm_loader, session_user): - result = neon_cli().emulate( - evm_loader.loader_id, - user_account.eth_address.hex(), - session_user.eth_address.hex(), - data=None - ) - assert result['exit_status'] == 'succeed', f"The 'exit_status' field is not succeed. Result: {result}" - assert result['steps_executed'] == 1, f"Steps executed amount is not 1. Result: {result}" - assert result['used_gas'] > 0, f"Used gas is less than 0. Result: {result}" - - -def test_emulate_contract_deploy(user_account, evm_loader): - contract_path = pytest.CONTRACTS_PATH / "hello_world.binary" - - with open(contract_path, 'rb') as f: - contract_code = f.read() - - result = neon_cli().emulate( - evm_loader.loader_id, - user_account.eth_address.hex(), - 'deploy', - contract_code.hex() - ) - assert result['exit_status'] == 'succeed', f"The 'exit_status' field is not succeed. Result: {result}" - assert result['steps_executed'] > 0, f"Steps executed amount is not 0. Result: {result}" - assert result['used_gas'] > 0, f"Used gas is less than 0. Result: {result}" - - -def test_emulate_call_contract_function(user_account, evm_loader, operator_keypair, treasury_pool): - contract = deploy_contract(operator_keypair, user_account, "hello_world.binary", evm_loader, treasury_pool) - assert contract.eth_address - assert get_solana_balance(contract.solana_address) > 0 - data = abi.function_signature_to_4byte_selector('call_hello_world()') - result = neon_cli().emulate( - evm_loader.loader_id, - user_account.eth_address.hex(), - contract.eth_address.hex(), - data.hex() - ) - - assert result['exit_status'] == 'succeed', f"The 'exit_status' field is not succeed. Result: {result}" - assert result['steps_executed'] > 0, f"Steps executed amount is 0. Result: {result}" - assert result['used_gas'] > 0, f"Used gas is less than 0. Result: {result}" - assert "Hello World" in to_text(result["result"]) - - -def test_neon_elf_params(evm_loader): - result = neon_cli().call(f"--evm_loader={evm_loader.loader_id} neon-elf-params") - some_fields = ['NEON_CHAIN_ID', 'NEON_TOKEN_MINT', 'NEON_REVISION'] - for field in some_fields: - assert field in result, f"The field {field} is not in result {result}" - assert result[field] != "", f"The value for fiels {field} is empty" - - -def test_collect_treasury(evm_loader): - command_args = f"collect-treasury --evm_loader {evm_loader.loader_id}" - index = random.randint(0, 127) - treasury_pool_address = create_treasury_pool_address(index) - result = neon_cli().call(command_args) - main_pool_address = PublicKey(result["pool_address"]) - balance_before = get_solana_balance(main_pool_address) - - amount = random.randint(1, 1000) - trx = solana_client.request_airdrop(treasury_pool_address, amount) - wait_confirm_transaction(solana_client, trx.value) - result = neon_cli().call(command_args) - - balance_after = get_solana_balance(PublicKey(main_pool_address)) - assert balance_after >= balance_before + amount - - -def test_init_environment(evm_loader): - result = neon_cli().call(f"init-environment --evm_loader {evm_loader.loader_id}") - assert len(result["transactions"]) == 0 - - -def test_get_ether_account_data(evm_loader, user_account): - result = neon_cli().call( - f"get-ether-account-data --evm_loader {evm_loader.loader_id} {user_account.eth_address.hex()}") - - assert f"0x{user_account.eth_address.hex()}" == result["address"] - assert str(user_account.solana_account_address) == result["solana_address"] - - assert solana_client.get_account_info(user_account.solana_account.public_key).value is not None - - -def test_create_ether_account(evm_loader): - acc = gen_hash_of_block(20) - result = neon_cli().call( - f"create-ether-account --evm_loader {evm_loader.loader_id} {acc}") - - acc_info = solana_client.get_account_info(PublicKey(result['solana_address']), commitment=Confirmed) - assert acc_info.value is not None - - -def test_deposit(evm_loader, user_account): - amount = random.randint(1, 100000) - result = neon_cli().call( - f"deposit --evm_loader {evm_loader.loader_id} {amount} {user_account.eth_address.hex()}") - balance_after = get_neon_balance(solana_client, user_account.solana_account_address) - assert result["transaction"] is not None - assert balance_after == amount * 1000000000 - - -def test_get_storage_at(evm_loader, operator_keypair, user_account, treasury_pool): - contract = deploy_contract(operator_keypair, user_account, "hello_world.binary", evm_loader, treasury_pool) - expected_storage = '0000000000000000000000000000000000000000000000000000000000000005' - result = neon_cli().call( - f"get-storage-at --evm_loader {evm_loader.loader_id} {contract.eth_address.hex()} 0x0") - assert result == expected_storage - - -def test_cancel_trx(evm_loader, user_account, rw_lock_contract, operator_keypair, treasury_pool): - func_name = abi.function_signature_to_4byte_selector('unchange_storage(uint8,uint8)') - data = (func_name + bytes.fromhex("%064x" % 0x01) + bytes.fromhex("%064x" % 0x01)) - - eth_transaction = make_eth_transaction( - rw_lock_contract.eth_address, - data, - user_account.solana_account, - user_account.solana_account_address, - ) - storage_account = create_holder(operator_keypair) - instruction = eth_transaction.rawTransaction - trx = TransactionWithComputeBudget(operator_keypair) - trx.add( - make_PartialCallOrContinueFromRawEthereumTX( - instruction, - operator_keypair, evm_loader, storage_account, treasury_pool.account, treasury_pool.buffer, 1, - [ - rw_lock_contract.solana_address, - user_account.solana_account_address, - ] - ) - ) - solana_client = Client(SOLANA_URL) - - receipt = send_transaction(solana_client, trx, operator_keypair) - assert receipt.value.transaction.meta.err is None - user_nonce = get_transaction_count(solana_client, user_account.solana_account_address) - - result = neon_cli().call(f"cancel-trx --evm_loader={evm_loader.loader_id} {storage_account}") - assert result["transaction"] is not None - assert user_nonce < get_transaction_count(solana_client, user_account.solana_account_address) diff --git a/evm_loader/tests/test_execute_trx_from_instruction.py b/evm_loader/tests/test_execute_trx_from_instruction.py deleted file mode 100644 index 534b3348d..000000000 --- a/evm_loader/tests/test_execute_trx_from_instruction.py +++ /dev/null @@ -1,277 +0,0 @@ -import random -import string - -import pytest -import solana -import eth_abi -from eth_keys import keys as eth_keys -from eth_utils import abi, to_text -from solana.keypair import Keypair -from solana.publickey import PublicKey -from solana.rpc.commitment import Confirmed -from spl.token.instructions import get_associated_token_address - -from .solana_utils import execute_trx_from_instruction, solana_client, get_neon_balance, neon_cli -from .utils.assert_messages import InstructionAsserts -from .utils.constants import NEON_TOKEN_MINT_ID -from .utils.contract import deploy_contract, make_contract_call_trx -from .utils.ethereum import make_eth_transaction -from .utils.transaction_checks import check_transaction_logs_have_text -from .utils.types import Caller - - -class TestExecuteTrxFromInstruction: - - def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, sender_with_tokens, session_user, - evm_loader): - amount = 10 - sender_balance_before = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - recipient_balance_before = get_neon_balance(solana_client, session_user.solana_account_address) - - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount) - resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - operator_keypair) - sender_balance_after = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - recipient_balance_after = get_neon_balance(solana_client, session_user.solana_account_address) - assert sender_balance_before - amount == sender_balance_after - assert recipient_balance_before + amount == recipient_balance_after - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - def test_transfer_transaction_with_non_existing_recipient(self, operator_keypair, treasury_pool, sender_with_tokens, - evm_loader): - # recipient account should be created - recipient = Keypair.generate() - - recipient_ether = eth_keys.PrivateKey(recipient.secret_key[:32]).public_key.to_canonical_address() - recipient_solana_address, _ = evm_loader.ether2program(recipient_ether) - amount = 10 - signed_tx = make_eth_transaction(recipient_ether, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount) - resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - PublicKey(recipient_solana_address)], - operator_keypair) - recipient_balance_after = get_neon_balance(solana_client, PublicKey(recipient_solana_address)) - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - assert recipient_balance_after == amount - - def test_call_contract_function_without_neon_transfer(self, operator_keypair, treasury_pool, sender_with_tokens, - evm_loader, string_setter_contract): - text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) - signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) - - resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - string_setter_contract.solana_address], - operator_keypair) - - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - assert text in to_text( - neon_cli().call_contract_get_function(evm_loader, sender_with_tokens, string_setter_contract, - "get()")) - - def test_call_contract_function_with_neon_transfer(self, operator_keypair, treasury_pool, sender_with_tokens, - evm_loader): - transfer_amount = random.randint(1, 1000) - - contract = deploy_contract(operator_keypair, sender_with_tokens, "string_setter.binary", evm_loader, - treasury_pool) - - sender_balance_before = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - contract_balance_before = get_neon_balance(solana_client, contract.solana_address) - - text = ''.join(random.choice(string.ascii_letters) for i in range(10)) - func_name = abi.function_signature_to_4byte_selector('set(string)') - data = func_name + eth_abi.encode(['string'], [text]) - signed_tx = make_eth_transaction(contract.eth_address, data, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, transfer_amount) - resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - contract.solana_address], - operator_keypair) - - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - assert text in to_text(neon_cli().call_contract_get_function(evm_loader, sender_with_tokens, contract, "get()")) - - sender_balance_after = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - contract_balance_after = get_neon_balance(solana_client, contract.solana_address) - assert sender_balance_before - transfer_amount == sender_balance_after - assert contract_balance_before + transfer_amount == contract_balance_after - - def test_incorrect_chain_id(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, session_user): - amount = 1 - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount, chain_id=1) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_CHAIN_ID): - execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - operator_keypair) - - def test_incorrect_nonce(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, session_user): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - - execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - operator_keypair) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_NONCE): - execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - operator_keypair) - - def test_insufficient_funds(self, operator_keypair, treasury_pool, evm_loader, sender_with_tokens, session_user): - user_balance = get_neon_balance(solana_client, session_user.solana_account_address) - - signed_tx = make_eth_transaction(sender_with_tokens.eth_address, None, session_user.solana_account, - session_user.solana_account_address, user_balance + 1) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INSUFFICIENT_FUNDS): - execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - operator_keypair) - - def test_gas_limit_reached(self, operator_keypair, treasury_pool, session_user, evm_loader, sender_with_tokens): - amount = 10 - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount, gas=1) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.OUT_OF_GAS): - execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], - operator_keypair) - - def test_sender_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, session_user, - sender_with_tokens, evm_loader): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): - execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [session_user.solana_account_address], - operator_keypair) - - def test_recipient_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, sender_with_tokens, - evm_loader, session_user): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): - execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address], - operator_keypair) - - def test_incorrect_treasure_pool(self, operator_keypair, sender_with_tokens, evm_loader, session_user): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - treasury_buffer = b'\x02\x00\x00\x00' - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_TREASURE_ACC): - execute_trx_from_instruction(operator_keypair, evm_loader, Keypair().public_key, treasury_buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - operator_keypair) - - def test_incorrect_treasure_index(self, operator_keypair, sender_with_tokens, evm_loader, treasury_pool, - session_user): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - treasury_buffer = b'\x03\x00\x00\x00' - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_TREASURE_ACC): - execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - operator_keypair) - - def test_incorrect_operator_account(self, sender_with_tokens, evm_loader, treasury_pool, session_user): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - fake_operator = Keypair() - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ACC_NOT_FOUND): - execute_trx_from_instruction(fake_operator, evm_loader, treasury_pool.account, treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - fake_operator) - - def test_operator_is_not_in_white_list(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user): - # now any user can send transactions through "execute transaction from instruction" instruction - - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - resp = execute_trx_from_instruction(sender_with_tokens.solana_account, evm_loader, treasury_pool.account, - treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - sender_with_tokens.solana_account) - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - def test_incorrect_system_program(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - fake_sys_program_id = Keypair().public_key - with pytest.raises(solana.rpc.core.RPCException, - match=str.format(InstructionAsserts.NOT_SYSTEM_PROGRAM, fake_sys_program_id)): - execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, - treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - operator_keypair, system_program=fake_sys_program_id) - - def test_incorrect_neon_program(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - fake_neon_program_id = Keypair().public_key - with pytest.raises(solana.rpc.core.RPCException, - match=str.format(InstructionAsserts.NOT_NEON_PROGRAM, fake_neon_program_id)): - execute_trx_from_instruction(sender_with_tokens.solana_account, evm_loader, treasury_pool.account, - treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - sender_with_tokens.solana_account, evm_loader_public_key=fake_neon_program_id) - - def test_operator_does_not_have_enough_founds(self, sender_with_tokens, evm_loader, treasury_pool, - session_user): - key = Keypair.generate() - caller_ether = eth_keys.PrivateKey(key.secret_key[:32]).public_key.to_canonical_address() - caller, caller_nonce = evm_loader.ether2program(caller_ether) - caller_token = get_associated_token_address(PublicKey(caller), NEON_TOKEN_MINT_ID) - evm_loader.create_ether_account(caller_ether) - - operator_without_money = Caller(key, PublicKey(caller), caller_ether, caller_nonce, caller_token) - - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - with pytest.raises(solana.rpc.core.RPCException, - match="Attempt to debit an account but found no record of a prior credit"): - execute_trx_from_instruction(operator_without_money.solana_account, evm_loader, treasury_pool.account, - treasury_pool.buffer, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], - operator_without_money.solana_account) diff --git a/evm_loader/tests/test_holder_account.py b/evm_loader/tests/test_holder_account.py deleted file mode 100644 index 41eef0dc3..000000000 --- a/evm_loader/tests/test_holder_account.py +++ /dev/null @@ -1,151 +0,0 @@ -from hashlib import sha256 -from random import randrange - -import pytest -import solana -from solana.publickey import PublicKey -from solana.rpc.commitment import Confirmed -from solana.rpc.types import TxOpts -from solana.transaction import Transaction - -from . import solana_utils -from .solana_utils import solana_client, write_transaction_to_holder_account, \ - send_transaction_step_from_account, get_solana_balance, execute_transaction_steps_from_account -from .utils.assert_messages import InstructionAsserts -from .utils.constants import EVM_LOADER, TAG_STATE -from .utils.contract import make_deployment_transaction, make_contract_call_trx -from .utils.ethereum import make_eth_transaction -from .utils.instructions import make_WriteHolder -from .utils.layouts import STORAGE_ACCOUNT_INFO_LAYOUT, HOLDER_ACCOUNT_INFO_LAYOUT -from .utils.storage import create_holder, delete_holder - - -def transaction_from_holder(key: PublicKey): - data = solana_client.get_account_info(key, commitment=Confirmed).value.data - header = HOLDER_ACCOUNT_INFO_LAYOUT.parse(data) - - return data[HOLDER_ACCOUNT_INFO_LAYOUT.sizeof():][:header.len] - - -def test_create_holder_account(operator_keypair): - holder_acc = create_holder(operator_keypair) - info = solana_client.get_account_info(holder_acc, commitment=Confirmed) - assert info.value is not None, "Holder account is not created" - assert info.value.lamports == 1000000000, "Account balance is not correct" - - -def test_create_the_same_holder_account_by_another_user(operator_keypair, session_user): - seed = str(randrange(1000000)) - storage = PublicKey( - sha256(bytes(operator_keypair.public_key) + bytes(seed, 'utf8') + bytes(PublicKey(EVM_LOADER))).digest()) - create_holder(operator_keypair, seed=seed, storage=storage) - - trx = Transaction() - trx.add( - solana_utils.create_account_with_seed(session_user.solana_account.public_key, - session_user.solana_account.public_key, seed, 10 ** 9, 128 * 1024), - solana_utils.create_holder_account(storage, session_user.solana_account.public_key) - ) - - with pytest.raises(solana.rpc.core.RPCException, match='already initialized'): - solana_utils.send_transaction(solana_client, trx, session_user.solana_account) - - -def test_write_tx_to_holder(operator_keypair, session_user, second_session_user, evm_loader): - holder_acc = create_holder(operator_keypair) - signed_tx = make_eth_transaction(second_session_user.eth_address, None, session_user.solana_account, - session_user.solana_account_address, 10) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - assert signed_tx.rawTransaction == transaction_from_holder(holder_acc), \ - "Account data is not correct" - - -def test_write_tx_to_holder_in_parts(operator_keypair, session_user): - holder_acc = create_holder(operator_keypair) - contract_filename = "ERC20ForSplFactory.binary" - - signed_tx = make_deployment_transaction(session_user, contract_filename) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - assert signed_tx.rawTransaction == transaction_from_holder(holder_acc), \ - "Account data is not correct" - - -def test_write_tx_to_holder_by_no_owner(operator_keypair, session_user, second_session_user, evm_loader): - holder_acc = create_holder(operator_keypair) - - signed_tx = make_eth_transaction(second_session_user.eth_address, None, session_user.solana_account, - session_user.solana_account_address, 10) - with pytest.raises(solana.rpc.core.RPCException, match="invalid owner"): - write_transaction_to_holder_account(signed_tx, holder_acc, session_user.solana_account) - - -def test_delete_holder(operator_keypair): - holder_acc = create_holder(operator_keypair) - delete_holder(holder_acc, operator_keypair, operator_keypair) - info = solana_client.get_account_info(holder_acc, commitment=Confirmed) - assert info.value is None, "Holder account isn't deleted" - - -def test_success_refund_after_holder_deliting(operator_keypair): - holder_acc = create_holder(operator_keypair) - - pre_storage = get_solana_balance(holder_acc) - pre_acc = get_solana_balance(operator_keypair.public_key) - - delete_holder(holder_acc, operator_keypair, operator_keypair) - - post_acc = get_solana_balance(operator_keypair.public_key) - - assert pre_storage + pre_acc, post_acc + 5000 - - -def test_delete_holder_by_no_owner(operator_keypair, user_account): - holder_acc = create_holder(operator_keypair) - with pytest.raises(solana.rpc.core.RPCException, match="invalid owner"): - delete_holder(holder_acc, user_account.solana_account, user_account.solana_account) - - -def test_write_to_not_finalized_holder(rw_lock_contract, user_account, evm_loader, operator_keypair, treasury_pool, - new_holder_acc): - signed_tx = make_contract_call_trx(user_account, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) - write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) - - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 1, operator_keypair) - account_data = solana_client.get_account_info(new_holder_acc, commitment=Confirmed).value.data - parsed_data = STORAGE_ACCOUNT_INFO_LAYOUT.parse(account_data) - assert parsed_data.tag == TAG_STATE - - signed_tx2 = make_contract_call_trx(user_account, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) - - with pytest.raises(solana.rpc.core.RPCException, match="invalid tag"): - write_transaction_to_holder_account(signed_tx2, new_holder_acc, operator_keypair) - - -def test_write_to_finalized_holder(rw_lock_contract, session_user, evm_loader, operator_keypair, treasury_pool, - new_holder_acc): - signed_tx = make_contract_call_trx(session_user, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) - write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) - - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, - [session_user.solana_account_address, - rw_lock_contract.solana_address]) - signed_tx2 = make_contract_call_trx(session_user, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) - - write_transaction_to_holder_account(signed_tx2, new_holder_acc, operator_keypair) - assert signed_tx2.rawTransaction == transaction_from_holder(new_holder_acc), \ - "Account data is not correct" - - -@pytest.mark.parametrize("overflow_offset", [int(0xFFFFFFFFFFFFFFFF - 64), int(0xFFFFFFFFFFFFFFFF - 65)]) -def test_holder_write_overflow(operator_keypair, treasury_pool, evm_loader, - sender_with_tokens, session_user, holder_acc, overflow_offset): - trx = Transaction() - trx.add(make_WriteHolder(operator_keypair.public_key, holder_acc, b"\x00" * 32, overflow_offset, b"\x00" * 1)) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.HOLDER_OVERFLOW): - solana_client.send_transaction( - trx, - operator_keypair, - opts=TxOpts(skip_confirmation=True, preflight_commitment=Confirmed), - ) diff --git a/evm_loader/tests/test_parallel.py b/evm_loader/tests/test_parallel.py deleted file mode 100644 index 1a747f04c..000000000 --- a/evm_loader/tests/test_parallel.py +++ /dev/null @@ -1,166 +0,0 @@ -from typing import Any -from unittest import TestCase - -from _pytest.fixtures import fixture -from solana.keypair import Keypair -from solana.publickey import PublicKey -from solana.rpc.core import RPCException - -from .solana_utils import EvmLoader, solana_client, get_account_data, make_new_user, deposit_neon, \ - cancel_transaction, send_transaction_step_from_account, execute_trx_from_instruction -from .utils.contract import write_transaction_to_holder_account, make_deployment_transaction -from .utils.ethereum import create_contract_address, make_eth_transaction -from .utils.layouts import ACCOUNT_INFO_LAYOUT -from .utils.storage import create_holder -from .utils.types import Caller, TreasuryPool - -EVM_STEPS_COUNT = 0xFFFFFFFF_FFFFFFFF -ONE_TOKEN = 10 ** 9 -BIG_CONTRACT_FILENAME = "BigContract.binary" -MAX_PERMITTED_DATA_INCREASE = 10240 - - -class ParallelTransactionsTest(TestCase): - @fixture(autouse=True) - def prepare_fixture( - self, - user_account: Caller, - evm_loader: EvmLoader, - operator_keypair: Keypair, - treasury_pool: TreasuryPool, - ): - self.user_account = user_account - self.evm_loader = evm_loader - self.operator_keypair = operator_keypair - self.treasury_pool = treasury_pool - self.second_account = make_new_user(evm_loader) - - def test_create_same_accounts(self): - cases = [ - [2, ACCOUNT_INFO_LAYOUT.sizeof()], - [3, MAX_PERMITTED_DATA_INCREASE], - [4, MAX_PERMITTED_DATA_INCREASE * 2], - ] - - for case in cases: - iterations = case[0] - expected_length = case[1] - with self.subTest(iterations=iterations, expected_length=expected_length): - self.create_same_accounts_subtest(iterations, expected_length) - - def create_same_accounts_subtest(self, iterations: int, expected_length: int): - deposit_neon(self.evm_loader, self.operator_keypair, self.user_account.eth_address, ONE_TOKEN) - deposit_neon(self.evm_loader, self.operator_keypair, self.second_account.eth_address, ONE_TOKEN) - - contract = create_contract_address(self.user_account, self.evm_loader) - holder_acc = create_holder(self.operator_keypair) - deployment_tx = make_deployment_transaction(self.user_account, BIG_CONTRACT_FILENAME) - write_transaction_to_holder_account(deployment_tx, holder_acc, self.operator_keypair) - - # First N iterations - for i in range(iterations): - deployment_receipt = send_transaction_step_from_account(self.operator_keypair, - self.evm_loader, - self.treasury_pool, - holder_acc, - [contract.solana_address, - self.user_account.solana_account_address], - EVM_STEPS_COUNT, - self.operator_keypair) - - assert not ParallelTransactionsTest.check_iteration_deployed(deployment_receipt) - - # Transferring to the same account in order to break deployment - ParallelTransactionsTest.transfer( - self.second_account, - contract.eth_address, - ONE_TOKEN, - contract.solana_address, - self.evm_loader, - self.operator_keypair, - self.treasury_pool, - ) - - # Trying to finish deployment (expected to fail) - try: - send_transaction_step_from_account(self.operator_keypair, - self.evm_loader, - self.treasury_pool, - holder_acc, - [contract.solana_address, - self.user_account.solana_account_address], - EVM_STEPS_COUNT, - self.operator_keypair) - - assert False, 'Deployment expected to fail' - except RPCException as e: - ParallelTransactionsTest.check_account_initialized_in_another_trx_exception(e, contract.solana_address) - - # Cancel deployment transaction: - cancel_transaction( - deployment_tx.hash, - holder_acc, - self.operator_keypair, - [contract.solana_address, self.user_account.solana_account_address], - ) - - data = get_account_data(solana_client, contract.solana_address, expected_length) - assert len(data) == expected_length - - account = ACCOUNT_INFO_LAYOUT.parse(data) - assert account.code_size == 0 - balance = int.from_bytes(account.balance, byteorder="little") - assert balance == ONE_TOKEN - - @staticmethod - def check_iteration_deployed(receipt: Any) -> bool: - if receipt.value.transaction.meta.err: - raise AssertionError(f"Can't deploy contract: {receipt.value.transaction.meta.err}") - - for log in receipt.value.transaction.meta.log_messages: - if "exit_status" in log: - return True - if "ExitError" in log: - raise AssertionError(f"EVM Return error in logs: {receipt}") - return False - - @staticmethod - def transfer( - src_account: Caller, - dst_addr: bytes, - value: int, - dst_solana_addr: PublicKey, - evm_loader: EvmLoader, - operator_keypair: Keypair, - treasury_pool: TreasuryPool, - ): - message = make_eth_transaction( - dst_addr, - bytes(), - src_account.solana_account, - src_account.solana_account_address, - value, - ) - - trx = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, - message, - [dst_solana_addr, - src_account.solana_account_address], - operator_keypair) - receipt = solana_client.get_transaction(trx.value) - print("Transfer receipt:", receipt) - assert receipt.value.transaction.meta.err is None - - return receipt - - @staticmethod - def check_account_initialized_in_another_trx_exception(exception: RPCException, solana_address: PublicKey): - error = exception.args[0] - print("error:", error) - assert 'instruction requires an uninitialized account' in error.message - - for log in error.data.logs: - if f'Blocked nonexistent account {solana_address} was created/initialized outside' in log: - return - - assert False, "Search string not found in Solana logs" diff --git a/evm_loader/tests/test_step_instructions_work_the_same.py b/evm_loader/tests/test_step_instructions_work_the_same.py deleted file mode 100644 index d9ed80e23..000000000 --- a/evm_loader/tests/test_step_instructions_work_the_same.py +++ /dev/null @@ -1,56 +0,0 @@ -from .solana_utils import solana_client, execute_transaction_steps_from_account, write_transaction_to_holder_account, \ - execute_transaction_steps_from_instruction -from .utils.contract import make_deployment_transaction -from .utils.ethereum import make_eth_transaction, create_contract_address -from .utils.storage import create_holder - - - -class TestTransactionStepFromAccount: - - def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, evm_loader, - sender_with_tokens, session_user, holder_acc): - amount = 10 - - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - resp_from_acc = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0).value - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount) - signature = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, [session_user.solana_account_address, - sender_with_tokens.solana_account_address], - 0) - resp_from_inst = solana_client.get_transaction(signature.value).value - assert resp_from_acc.transaction.meta.fee == resp_from_inst.transaction.meta.fee - assert resp_from_acc.transaction.meta.inner_instructions == resp_from_inst.transaction.meta.inner_instructions - for i in range(len(resp_from_acc.transaction.meta.post_balances)): - assert resp_from_acc.transaction.meta.post_balances[i] - resp_from_acc.transaction.meta.pre_balances[i] == \ - resp_from_inst.transaction.meta.post_balances[i] - resp_from_inst.transaction.meta.pre_balances[i] - - def test_deploy_contract(self, operator_keypair, holder_acc, treasury_pool, evm_loader, sender_with_tokens): - contract_filename = "small.binary" - contract = create_contract_address(sender_with_tokens, evm_loader) - - signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - resp_from_acc = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [contract.solana_address, - sender_with_tokens.solana_account_address]).value - signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename) - holder_acc = create_holder(operator_keypair) - contract = create_contract_address(sender_with_tokens, evm_loader) - - signature = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, [contract.solana_address, - sender_with_tokens.solana_account_address]) - resp_from_inst = solana_client.get_transaction(signature.value).value - assert resp_from_acc.transaction.meta.fee == resp_from_inst.transaction.meta.fee - assert len(resp_from_acc.transaction.meta.inner_instructions) == len( - resp_from_inst.transaction.meta.inner_instructions) - assert len(resp_from_acc.transaction.transaction.message.account_keys) == len( - resp_from_acc.transaction.transaction.message.account_keys) diff --git a/evm_loader/tests/test_transaction_step_from_account.py b/evm_loader/tests/test_transaction_step_from_account.py deleted file mode 100644 index 4141e60ab..000000000 --- a/evm_loader/tests/test_transaction_step_from_account.py +++ /dev/null @@ -1,483 +0,0 @@ -import json -import random -import string -import time - -import eth_abi -import pytest -import solana -from eth_keys import keys as eth_keys -from eth_utils import abi, to_text, to_int -from solana.keypair import Keypair -from solana.publickey import PublicKey -from solana.rpc.commitment import Processed, Confirmed -from solana.rpc.types import TxOpts - -from .solana_utils import get_neon_balance, solana_client, neon_cli, execute_transaction_steps_from_account, \ - write_transaction_to_holder_account, create_treasury_pool_address, send_transaction_step_from_account -from .utils.assert_messages import InstructionAsserts -from .utils.constants import TAG_FINALIZED_STATE -from .utils.contract import make_deployment_transaction, make_contract_call_trx, deploy_contract -from .utils.ethereum import make_eth_transaction, create_contract_address -from .utils.instructions import TransactionWithComputeBudget, make_ExecuteTrxFromAccountDataIterativeOrContinue -from .utils.layouts import FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT -from .utils.storage import create_holder -from .utils.transaction_checks import check_transaction_logs_have_text, check_holder_account_tag -from .utils.types import TreasuryPool - - -class TestTransactionStepFromAccount: - - def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, evm_loader, - sender_with_tokens, session_user, holder_acc): - amount = 10 - sender_balance_before = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - recipient_balance_before = get_neon_balance(solana_client, session_user.solana_account_address) - - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - sender_balance_after = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - recipient_balance_after = get_neon_balance(solana_client, session_user.solana_account_address) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") - assert sender_balance_before - amount == sender_balance_after - assert recipient_balance_before + amount == recipient_balance_after - - def test_deploy_contract(self, operator_keypair, holder_acc, treasury_pool, evm_loader, sender_with_tokens): - contract_filename = "hello_world.binary" - contract = create_contract_address(sender_with_tokens, evm_loader) - - signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - contract_path = pytest.CONTRACTS_PATH / contract_filename - with open(contract_path, 'rb') as f: - contract_code = f.read() - - steps_count = neon_cli().get_steps_count(evm_loader, sender_with_tokens, "deploy", contract_code.hex()) - resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [contract.solana_address, - sender_with_tokens.solana_account_address], - steps_count) - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x12") - - def test_call_contract_function_without_neon_transfer(self, operator_keypair, holder_acc, treasury_pool, - sender_with_tokens, evm_loader, string_setter_contract): - text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) - signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [string_setter_contract.solana_address, - sender_with_tokens.solana_account_address]) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") - - assert text in to_text( - neon_cli().call_contract_get_function(evm_loader, sender_with_tokens, string_setter_contract, - "get()")) - - def test_call_contract_function_with_neon_transfer(self, operator_keypair, treasury_pool, - sender_with_tokens, string_setter_contract, holder_acc, - evm_loader): - transfer_amount = random.randint(1, 1000) - - sender_balance_before = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - contract_balance_before = get_neon_balance(solana_client, string_setter_contract.solana_address) - - text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) - - signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text], - value=transfer_amount) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [string_setter_contract.solana_address, - sender_with_tokens.solana_account_address] - ) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") - - sender_balance_after = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - contract_balance_after = get_neon_balance(solana_client, string_setter_contract.solana_address) - assert sender_balance_before - transfer_amount == sender_balance_after - assert contract_balance_before + transfer_amount == contract_balance_after - - assert text in to_text( - neon_cli().call_contract_get_function(evm_loader, sender_with_tokens, string_setter_contract, - "get()")) - - def test_transfer_transaction_with_non_existing_recipient(self, operator_keypair, holder_acc, treasury_pool, - sender_with_tokens, evm_loader): - # recipient account should be created - recipient = Keypair.generate() - recipient_ether = eth_keys.PrivateKey(recipient.secret_key[:32]).public_key.to_canonical_address() - recipient_solana_address, _ = evm_loader.ether2program(recipient_ether) - amount = 10 - signed_tx = make_eth_transaction(recipient_ether, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [PublicKey(recipient_solana_address), - sender_with_tokens.solana_account_address], 0) - - recipient_balance_after = get_neon_balance(solana_client, PublicKey(recipient_solana_address)) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") - - assert recipient_balance_after == amount - - def test_incorrect_chain_id(self, operator_keypair, holder_acc, treasury_pool, - sender_with_tokens, session_user, evm_loader): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1, chain_id=1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_CHAIN_ID): - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_incorrect_nonce(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, session_user, - holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_NONCE): - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_run_finalized_transaction(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.TRX_ALREADY_FINALIZED): - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_insufficient_funds(self, operator_keypair, treasury_pool, evm_loader, session_user, - holder_acc, sender_with_tokens): - user_balance = get_neon_balance(solana_client, session_user.solana_account_address) - - signed_tx = make_eth_transaction(sender_with_tokens.eth_address, None, session_user.solana_account, - session_user.solana_account_address, user_balance + 1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INSUFFICIENT_FUNDS): - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_gas_limit_reached(self, operator_keypair, treasury_pool, session_user, evm_loader, sender_with_tokens, - holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 10, gas=1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.OUT_OF_GAS): - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_sender_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, session_user, - sender_with_tokens, evm_loader, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [session_user.solana_account_address], 0) - - def test_recipient_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, session_user, - sender_with_tokens, evm_loader, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [sender_with_tokens.solana_account_address], 0) - - def test_incorrect_treasure_pool(self, operator_keypair, sender_with_tokens, evm_loader, session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - index = 2 - treasury = TreasuryPool(index, Keypair().generate().public_key, index.to_bytes(4, 'little')) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_TREASURE_ACC): - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury, holder_acc, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 0) - - def test_incorrect_treasure_index(self, operator_keypair, sender_with_tokens, evm_loader, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - index = 2 - treasury = TreasuryPool(index, create_treasury_pool_address(index), (index + 1).to_bytes(4, 'little')) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_TREASURE_ACC): - execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury, holder_acc, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 0) - - def test_incorrect_operator_account(self, operator_keypair, sender_with_tokens, evm_loader, treasury_pool, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - fake_operator = Keypair() - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ACC_NOT_FOUND): - execute_transaction_steps_from_account(fake_operator, evm_loader, treasury_pool, holder_acc, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 0) - - def test_operator_is_not_in_white_list(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.NOT_AUTHORIZED_OPERATOR): - execute_transaction_steps_from_account(sender_with_tokens.solana_account, evm_loader, treasury_pool, - holder_acc, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 0, - signer=sender_with_tokens.solana_account) - - def test_incorrect_system_program(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - fake_sys_program_id = Keypair().public_key - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, - match=str.format(InstructionAsserts.NOT_SYSTEM_PROGRAM, fake_sys_program_id)): - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 1, operator_keypair, - system_program=fake_sys_program_id) - - def test_incorrect_neon_program(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - fake_neon_program_id = Keypair().public_key - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, - match=str.format(InstructionAsserts.NOT_NEON_PROGRAM, fake_neon_program_id)): - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 1, operator_keypair, - evm_loader_public_key=fake_neon_program_id) - - def test_incorrect_holder_account(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user): - fake_holder_acc = Keypair.generate().public_key - with pytest.raises(solana.rpc.core.RPCException, - match=str.format(InstructionAsserts.NOT_PROGRAM_OWNED, fake_holder_acc)): - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, fake_holder_acc, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 1, operator_keypair) - - -class TestAccountStepContractCallContractInteractions: - def test_contract_call_unchange_storage_function(self, rw_lock_contract, rw_lock_caller, session_user, evm_loader, - operator_keypair, treasury_pool, holder_acc): - signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'unchange_storage(uint8,uint8)', [1, 1]) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [rw_lock_caller.solana_address, - rw_lock_contract.solana_address, - session_user.solana_account_address]) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x12") - - def test_contract_call_set_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, - treasury_pool, holder_acc, rw_lock_caller): - signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'update_storage_str(string)', ['hello']) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [rw_lock_caller.solana_address, - rw_lock_contract.solana_address, - session_user.solana_account_address], 1000) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") - - assert 'hello' in to_text(neon_cli().call_contract_get_function(evm_loader, session_user, rw_lock_contract, - "get_text()")) - - def test_contract_call_get_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, - treasury_pool, holder_acc, rw_lock_caller): - signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'get_text()') - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - [rw_lock_caller.solana_address, - rw_lock_contract.solana_address, - session_user.solana_account_address], 1000) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x12") - - def test_contract_call_update_storage_map_function(self, rw_lock_contract, session_user, evm_loader, - operator_keypair, rw_lock_caller, - treasury_pool, holder_acc): - signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'update_storage_map(uint256)', [3]) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - func_name = abi.function_signature_to_4byte_selector('update_storage_map(uint256)') - data = func_name + eth_abi.encode(['uint256'], [3]) - result = neon_cli().emulate(evm_loader.loader_id, - session_user.eth_address.hex(), - rw_lock_caller.eth_address.hex(), - data.hex()) - additional_accounts = [session_user.solana_account_address, rw_lock_contract.solana_address, - rw_lock_caller.solana_address] - for acc in result['solana_accounts']: - additional_accounts.append(PublicKey(acc['pubkey'])) - - resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, - additional_accounts) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") - - constructor_args = eth_abi.encode(['address', 'uint256'], [rw_lock_caller.eth_address.hex(), 2]) - actual_data = neon_cli().call_contract_get_function(evm_loader, session_user, rw_lock_contract, - "data(address,uint256)", constructor_args) - assert to_int(hexstr=actual_data) == 2, "Contract data is not correct" - - -class TestTransactionStepFromAccountParallelRuns: - - def test_one_user_call_2_contracts(self, rw_lock_contract, string_setter_contract, user_account, evm_loader, - operator_keypair, treasury_pool, new_holder_acc): - signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'unchange_storage(uint8,uint8)', [1, 1]) - write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) - - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 1, operator_keypair) - - signed_tx2 = make_contract_call_trx(user_account, string_setter_contract, 'get()') - - holder_acc2 = create_holder(operator_keypair) - write_transaction_to_holder_account(signed_tx2, holder_acc2, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc2, - [user_account.solana_account_address, - string_setter_contract.solana_address], 1, operator_keypair) - - def test_2_users_call_the_same_contract(self, rw_lock_contract, user_account, - session_user, evm_loader, operator_keypair, - treasury_pool, new_holder_acc): - signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'unchange_storage(uint8,uint8)', [1, 1]) - write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) - - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 1, operator_keypair) - - signed_tx2 = make_contract_call_trx(session_user, rw_lock_contract, 'get_text()') - - holder_acc2 = create_holder(operator_keypair) - write_transaction_to_holder_account(signed_tx2, holder_acc2, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc2, - [session_user.solana_account_address, - rw_lock_contract.solana_address], 1, operator_keypair) - - def test_two_contracts_call_same_contract(self, rw_lock_contract, user_account, - session_user, evm_loader, operator_keypair, - treasury_pool, new_holder_acc): - constructor_args = eth_abi.encode(['address'], [rw_lock_contract.eth_address.hex()]) - - contract1 = deploy_contract(operator_keypair, session_user, "rw_lock_caller.binary", evm_loader, treasury_pool, - encoded_args=constructor_args) - contract2 = deploy_contract(operator_keypair, session_user, "rw_lock_caller.binary", evm_loader, treasury_pool, - encoded_args=constructor_args) - - signed_tx1 = make_contract_call_trx(user_account, contract1, 'unchange_storage(uint8,uint8)', [1, 1]) - signed_tx2 = make_contract_call_trx(session_user, contract2, 'get_text()') - write_transaction_to_holder_account(signed_tx1, new_holder_acc, operator_keypair) - - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, - [user_account.solana_account_address, - rw_lock_contract.solana_address, - contract1.solana_address], 1, operator_keypair) - - holder_acc2 = create_holder(operator_keypair) - write_transaction_to_holder_account(signed_tx2, holder_acc2, operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): - send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc2, - [session_user.solana_account_address, - rw_lock_contract.solana_address, - contract2.solana_address], 1, - operator_keypair) - - -class TestStepFromAccountChangingOperatorsDuringTrxRun: - def test_next_operator_can_continue_trx_after_some_time(self, rw_lock_contract, user_account, evm_loader, - operator_keypair, second_operator_keypair, treasury_pool, - new_holder_acc): - signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'update_storage_str(string)', ['text']) - write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) - - trx = TransactionWithComputeBudget(operator_keypair) - trx.add( - make_ExecuteTrxFromAccountDataIterativeOrContinue( - operator_keypair, evm_loader, new_holder_acc, treasury_pool.account, treasury_pool.buffer, 1, - [user_account.solana_account_address, - rw_lock_contract.solana_address] - ) - ) - solana_client.send_transaction(trx, operator_keypair, - opts=TxOpts(skip_confirmation=True, preflight_commitment=Confirmed)) - - # next operator can't continue trx during OPERATOR_PRIORITY_SLOTS*0.4 - with pytest.raises(solana.rpc.core.RPCException, - match=rf"{InstructionAsserts.INVALID_OPERATOR_KEY}|{InstructionAsserts.INVALID_HOLDER_OWNER}"): send_transaction_step_from_account(second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 500, second_operator_keypair) - - time.sleep(15) - send_transaction_step_from_account(second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 500, second_operator_keypair) - resp = send_transaction_step_from_account(second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 1, second_operator_keypair) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") diff --git a/evm_loader/tests/test_transaction_step_from_account_no_chainid.py b/evm_loader/tests/test_transaction_step_from_account_no_chainid.py deleted file mode 100644 index c53ab0528..000000000 --- a/evm_loader/tests/test_transaction_step_from_account_no_chainid.py +++ /dev/null @@ -1,90 +0,0 @@ -import random -import string - -import pytest -from eth_utils import to_text - -from .solana_utils import write_transaction_to_holder_account, get_neon_balance, solana_client, \ - execute_transaction_steps_from_account_no_chain_id, neon_cli -from .utils.constants import TAG_FINALIZED_STATE -from .utils.contract import make_deployment_transaction, make_contract_call_trx -from .utils.ethereum import make_eth_transaction, create_contract_address -from .utils.layouts import FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT -from .utils.transaction_checks import check_holder_account_tag, check_transaction_logs_have_text - - -class TestTransactionStepFromAccountNoChainId: - - def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, evm_loader, - sender_with_tokens, session_user, holder_acc): - amount = 10 - sender_balance_before = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - recipient_balance_before = get_neon_balance(solana_client, session_user.solana_account_address) - - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount, chain_id=None) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - resp = execute_transaction_steps_from_account_no_chain_id(operator_keypair, evm_loader, treasury_pool, - holder_acc, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - sender_balance_after = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - recipient_balance_after = get_neon_balance(solana_client, session_user.solana_account_address) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") - assert sender_balance_before - amount == sender_balance_after - assert recipient_balance_before + amount == recipient_balance_after - - def test_deploy_contract(self, operator_keypair, holder_acc, treasury_pool, evm_loader, sender_with_tokens): - contract_filename = "hello_world.binary" - contract = create_contract_address(sender_with_tokens, evm_loader) - - signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename, chain_id=None) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - contract_path = pytest.CONTRACTS_PATH / contract_filename - with open(contract_path, 'rb') as f: - contract_code = f.read() - - steps_count = neon_cli().get_steps_count(evm_loader, sender_with_tokens, "deploy", contract_code.hex()) - resp = execute_transaction_steps_from_account_no_chain_id(operator_keypair, evm_loader, treasury_pool, - holder_acc, - [contract.solana_address, - sender_with_tokens.solana_account_address], - steps_count) - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x12") - - def test_call_contract_function_with_neon_transfer(self, operator_keypair, treasury_pool, - sender_with_tokens, string_setter_contract, holder_acc, - evm_loader): - transfer_amount = random.randint(1, 1000) - - sender_balance_before = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - contract_balance_before = get_neon_balance(solana_client, string_setter_contract.solana_address) - - text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) - - signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text], - value=transfer_amount, chain_id=None) - write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) - - resp = execute_transaction_steps_from_account_no_chain_id(operator_keypair, evm_loader, treasury_pool, - holder_acc, - [string_setter_contract.solana_address, - sender_with_tokens.solana_account_address] - ) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") - - sender_balance_after = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - contract_balance_after = get_neon_balance(solana_client, string_setter_contract.solana_address) - assert sender_balance_before - transfer_amount == sender_balance_after - assert contract_balance_before + transfer_amount == contract_balance_after - - assert text in to_text( - neon_cli().call_contract_get_function(evm_loader, sender_with_tokens, string_setter_contract, - "get()")) diff --git a/evm_loader/tests/test_transaction_step_from_instruction.py b/evm_loader/tests/test_transaction_step_from_instruction.py deleted file mode 100644 index f3564bd68..000000000 --- a/evm_loader/tests/test_transaction_step_from_instruction.py +++ /dev/null @@ -1,506 +0,0 @@ -import json -import random -import string -import time - -import eth_abi -import pytest -import rlp -import solana -from eth_account.datastructures import SignedTransaction -from eth_keys import keys as eth_keys -from eth_utils import abi, to_text, to_int -from hexbytes import HexBytes -from solana.keypair import Keypair -from solana.publickey import PublicKey -from solana.rpc.commitment import Confirmed -from solana.rpc.core import RPCException - -from .solana_utils import get_neon_balance, solana_client, execute_transaction_steps_from_instruction, neon_cli, \ - create_treasury_pool_address, send_transaction_step_from_instruction -from .utils.assert_messages import InstructionAsserts -from .utils.constants import TAG_FINALIZED_STATE -from .utils.contract import make_deployment_transaction, make_contract_call_trx, deploy_contract -from .utils.ethereum import make_eth_transaction, create_contract_address -from .utils.layouts import FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT -from .utils.storage import create_holder -from .utils.transaction_checks import check_transaction_logs_have_text, check_holder_account_tag -from .utils.types import TreasuryPool - - -class TestTransactionStepFromInstruction: - - def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, evm_loader, - sender_with_tokens, session_user, holder_acc): - amount = 10 - sender_balance_before = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - recipient_balance_before = get_neon_balance(solana_client, session_user.solana_account_address) - - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount) - - resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - sender_balance_after = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - recipient_balance_after = get_neon_balance(solana_client, session_user.solana_account_address) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - assert sender_balance_before - amount == sender_balance_after - assert recipient_balance_before + amount == recipient_balance_after - - @pytest.mark.parametrize("chain_id", [None, 111]) - def test_deploy_contract(self, operator_keypair, holder_acc, treasury_pool, evm_loader, sender_with_tokens, - chain_id): - contract_filename = "small.binary" - - signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename, chain_id=chain_id) - contract = create_contract_address(sender_with_tokens, evm_loader) - - contract_path = pytest.CONTRACTS_PATH / contract_filename - with open(contract_path, 'rb') as f: - contract_code = f.read() - - steps_count = neon_cli().get_steps_count(evm_loader, sender_with_tokens, "deploy", contract_code.hex()) - resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, [contract.solana_address, - sender_with_tokens.solana_account_address], - steps_count) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value, "exit_status=0x12") - - def test_call_contract_function_without_neon_transfer(self, operator_keypair, treasury_pool, sender_with_tokens, - evm_loader, holder_acc, string_setter_contract): - text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) - signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) - - resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, [string_setter_contract.solana_address, - sender_with_tokens.solana_account_address] - ) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - assert text in to_text( - neon_cli().call_contract_get_function(evm_loader, sender_with_tokens, string_setter_contract, - "get()")) - - def test_call_contract_function_with_neon_transfer(self, operator_keypair, treasury_pool, sender_with_tokens, - evm_loader, holder_acc, string_setter_contract): - transfer_amount = random.randint(1, 1000) - - sender_balance_before = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - contract_balance_before = get_neon_balance(solana_client, string_setter_contract.solana_address) - - text = ''.join(random.choice(string.ascii_letters) for i in range(10)) - signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text], - value=transfer_amount) - - resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, [string_setter_contract.solana_address, - sender_with_tokens.solana_account_address] - ) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - sender_balance_after = get_neon_balance(solana_client, sender_with_tokens.solana_account_address) - contract_balance_after = get_neon_balance(solana_client, string_setter_contract.solana_address) - assert sender_balance_before - transfer_amount == sender_balance_after - assert contract_balance_before + transfer_amount == contract_balance_after - - assert text in to_text( - neon_cli().call_contract_get_function(evm_loader, sender_with_tokens, string_setter_contract, - "get()")) - - def test_transfer_transaction_with_non_existing_recipient(self, operator_keypair, treasury_pool, sender_with_tokens, - evm_loader, holder_acc): - # recipient account should be created - recipient = Keypair.generate() - recipient_ether = eth_keys.PrivateKey(recipient.secret_key[:32]).public_key.to_canonical_address() - recipient_solana_address, _ = evm_loader.ether2program(recipient_ether) - amount = 10 - signed_tx = make_eth_transaction(recipient_ether, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, amount) - - resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, [PublicKey(recipient_solana_address), - sender_with_tokens.solana_account_address], 0) - - recipient_balance_after = get_neon_balance(solana_client, PublicKey(recipient_solana_address)) - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - assert recipient_balance_after == amount - - def test_incorrect_chain_id(self, operator_keypair, holder_acc, treasury_pool, - sender_with_tokens, session_user, evm_loader): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1, chain_id=1) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_CHAIN_ID): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_incorrect_nonce(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, session_user, - holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - new_holder_acc = create_holder(operator_keypair) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_NONCE): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_run_finalized_transaction(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.TRX_ALREADY_FINALIZED): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_insufficient_funds(self, operator_keypair, treasury_pool, evm_loader, session_user, - holder_acc, sender_with_tokens): - user_balance = get_neon_balance(solana_client, session_user.solana_account_address) - - signed_tx = make_eth_transaction(sender_with_tokens.eth_address, None, session_user.solana_account, - session_user.solana_account_address, user_balance + 1) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INSUFFICIENT_FUNDS): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_gas_limit_reached(self, operator_keypair, treasury_pool, session_user, evm_loader, sender_with_tokens, - holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 10, gas=1) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.OUT_OF_GAS): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_sender_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, session_user, - sender_with_tokens, evm_loader, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [session_user.solana_account_address], 0) - - def test_recipient_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, session_user, - sender_with_tokens, evm_loader, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [session_user.solana_account_address], 0) - - def test_incorrect_treasure_pool(self, operator_keypair, sender_with_tokens, evm_loader, session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - index = 2 - treasury = TreasuryPool(index, Keypair().generate().public_key, index.to_bytes(4, 'little')) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_TREASURE_ACC): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury, holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_incorrect_treasure_index(self, operator_keypair, sender_with_tokens, evm_loader, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - index = 2 - treasury = TreasuryPool(index, create_treasury_pool_address(index), (index + 1).to_bytes(4, 'little')) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_TREASURE_ACC): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury, holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_incorrect_operator_account(self, sender_with_tokens, evm_loader, treasury_pool, session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - fake_operator = Keypair().generate() - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ACC_NOT_FOUND): - execute_transaction_steps_from_instruction(fake_operator, evm_loader, treasury_pool, holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0) - - def test_operator_is_not_in_white_list(self, sender_with_tokens, evm_loader, treasury_pool, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.NOT_AUTHORIZED_OPERATOR): - execute_transaction_steps_from_instruction(sender_with_tokens.solana_account, evm_loader, treasury_pool, - holder_acc, - signed_tx, - [session_user.solana_account_address, - sender_with_tokens.solana_account_address], 0, - signer=sender_with_tokens.solana_account) - - def test_incorrect_system_program(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - fake_sys_program_id = Keypair().generate().public_key - - with pytest.raises(solana.rpc.core.RPCException, - match=str.format(InstructionAsserts.NOT_SYSTEM_PROGRAM, fake_sys_program_id)): - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 1, operator_keypair, - system_program=fake_sys_program_id) - - def test_incorrect_neon_program(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user, holder_acc): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - fake_neon_program_id = Keypair().generate().public_key - - with pytest.raises(solana.rpc.core.RPCException, - match=str.format(InstructionAsserts.NOT_NEON_PROGRAM, fake_neon_program_id)): - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 1, operator_keypair, - evm_loader_public_key=fake_neon_program_id) - - def test_incorrect_holder_account(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, - session_user): - signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens.solana_account, - sender_with_tokens.solana_account_address, 1) - fake_holder_acc = Keypair.generate().public_key - with pytest.raises(solana.rpc.core.RPCException, - match=str.format(InstructionAsserts.NOT_PROGRAM_OWNED, fake_holder_acc)): - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, fake_holder_acc, - signed_tx, - [sender_with_tokens.solana_account_address, - session_user.solana_account_address], 1, operator_keypair) - - -class TestInstructionStepContractCallContractInteractions: - def test_contract_call_unchange_storage_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, - treasury_pool, holder_acc, rw_lock_caller): - signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'unchange_storage(uint8,uint8)', [1, 1]) - resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [rw_lock_caller.solana_address, - rw_lock_contract.solana_address, - session_user.solana_account_address]) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value, "exit_status=0x12") - - def test_contract_call_set_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, - treasury_pool, holder_acc, rw_lock_caller): - signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'update_storage_str(string)', ['hello']) - - resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [rw_lock_caller.solana_address, - rw_lock_contract.solana_address, - session_user.solana_account_address], 1000) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - assert 'hello' in to_text(neon_cli().call_contract_get_function(evm_loader, session_user, rw_lock_contract, - "get_text()")) - - def test_contract_call_get_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, - treasury_pool, holder_acc, rw_lock_caller): - signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'get_text()') - resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, - [rw_lock_caller.solana_address, - rw_lock_contract.solana_address, - session_user.solana_account_address], 1000) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value, "exit_status=0x12") - - def test_contract_call_update_storage_map_function(self, rw_lock_contract, session_user, evm_loader, - operator_keypair, rw_lock_caller, - treasury_pool, holder_acc): - signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'update_storage_map(uint256)', [3]) - - func_name = abi.function_signature_to_4byte_selector('update_storage_map(uint256)') - data = func_name + eth_abi.encode(['uint256'], [3]) - result = neon_cli().emulate(evm_loader.loader_id, - session_user.eth_address.hex(), - rw_lock_caller.eth_address.hex(), - data.hex()) - additional_accounts = [session_user.solana_account_address, rw_lock_contract.solana_address, - rw_lock_caller.solana_address] - for acc in result['solana_accounts']: - additional_accounts.append(PublicKey(acc['pubkey'])) - - resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx, additional_accounts) - - check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - constructor_args = eth_abi.encode(['address', 'uint256'], [rw_lock_caller.eth_address.hex(), 2]) - actual_data = neon_cli().call_contract_get_function(evm_loader, session_user, rw_lock_contract, - "data(address,uint256)", constructor_args) - assert to_int(hexstr=actual_data) == 2, "Contract data is not correct" - - -class TestTransactionStepFromInstructionParallelRuns: - - def test_one_user_call_2_contracts(self, rw_lock_contract, string_setter_contract, user_account, evm_loader, - operator_keypair, treasury_pool, new_holder_acc): - signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'unchange_storage(uint8,uint8)', [1, 1]) - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, signed_tx, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 1, operator_keypair) - - signed_tx2 = make_contract_call_trx(user_account, string_setter_contract, 'get()') - holder_acc2 = create_holder(operator_keypair) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc2, signed_tx2, - [user_account.solana_account_address, - string_setter_contract.solana_address], 1, operator_keypair) - - def test_2_users_call_the_same_contract(self, rw_lock_contract, user_account, - session_user, evm_loader, operator_keypair, - treasury_pool, new_holder_acc): - signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'unchange_storage(uint8,uint8)', [1, 1]) - - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, signed_tx, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 1, operator_keypair) - - signed_tx2 = make_contract_call_trx(session_user, rw_lock_contract, 'get_text()') - holder_acc2 = create_holder(operator_keypair) - - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc2, signed_tx2, - [session_user.solana_account_address, - rw_lock_contract.solana_address], 1, operator_keypair) - - def test_two_contracts_call_same_contract(self, rw_lock_contract, user_account, - session_user, evm_loader, operator_keypair, - treasury_pool, new_holder_acc): - constructor_args = eth_abi.encode(['address'], [rw_lock_contract.eth_address.hex()]) - - contract1 = deploy_contract(operator_keypair, session_user, "rw_lock_caller.binary", evm_loader, treasury_pool, - encoded_args=constructor_args) - contract2 = deploy_contract(operator_keypair, session_user, "rw_lock_caller.binary", evm_loader, treasury_pool, - encoded_args=constructor_args) - - signed_tx1 = make_contract_call_trx(user_account, contract1, 'unchange_storage(uint8,uint8)', [1, 1]) - signed_tx2 = make_contract_call_trx(session_user, contract2, 'get_text()') - - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, signed_tx1, - [user_account.solana_account_address, - rw_lock_contract.solana_address, - contract1.solana_address], 1, operator_keypair) - - holder_acc2 = create_holder(operator_keypair) - with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc2, signed_tx2, - [session_user.solana_account_address, - rw_lock_contract.solana_address, - contract2.solana_address], 1, - operator_keypair) - - -class TestStepFromInstructionChangingOperatorsDuringTrxRun: - def test_next_operator_can_continue_trx_after_some_time(self, rw_lock_contract, user_account, evm_loader, - operator_keypair, second_operator_keypair, treasury_pool, - new_holder_acc): - signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'update_storage_str(string)', ['text']) - - send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, - signed_tx, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 1, operator_keypair) - # next operator can't continue trx during OPERATOR_PRIORITY_SLOTS*0.4 - with pytest.raises(solana.rpc.core.RPCException, - match=rf"{InstructionAsserts.INVALID_OPERATOR_KEY}|{InstructionAsserts.INVALID_HOLDER_OWNER}"): - send_transaction_step_from_instruction(second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, - signed_tx, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 500, second_operator_keypair) - - time.sleep(15) - send_transaction_step_from_instruction(second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, - signed_tx, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 500, second_operator_keypair) - resp = send_transaction_step_from_instruction(second_operator_keypair, evm_loader, treasury_pool, - new_holder_acc, signed_tx, - [user_account.solana_account_address, - rw_lock_contract.solana_address], 1, second_operator_keypair) - check_transaction_logs_have_text(resp.value, "exit_status=0x11") - - -class TestStepFromInstructionWithChangedRLPTrx: - def test_add_waste_to_trx(self, sender_with_tokens, operator_keypair, treasury_pool, evm_loader, holder_acc, - string_setter_contract): - text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) - signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) - decoded_tx = rlp.decode(signed_tx.rawTransaction) - decoded_tx.insert(6, HexBytes(b'\x19p\x16l\xc0')) - new_trx = HexBytes(rlp.encode(decoded_tx)) - - signed_tx_new = SignedTransaction( - rawTransaction=new_trx, - hash=signed_tx.hash, - r=signed_tx.r, - s=signed_tx.s, - v=signed_tx.v, - ) - with pytest.raises(RPCException, match="Program log: RLP error: RlpIncorrectListLen"): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx_new, [sender_with_tokens.solana_account_address, - string_setter_contract.solana_address]) - - def test_add_waste_to_trx_without_decoding(self, sender_with_tokens, operator_keypair, treasury_pool, evm_loader, - holder_acc, - string_setter_contract): - text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) - signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) - signed_tx_new = SignedTransaction( - rawTransaction=signed_tx.rawTransaction + HexBytes(b'\x19p\x16l\xc0'), - hash=signed_tx.hash, - r=signed_tx.r, - s=signed_tx.s, - v=signed_tx.v, - ) - with pytest.raises(RPCException, match="Program log: RLP error: RlpInconsistentLengthAndData"): - execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, - signed_tx_new, [sender_with_tokens.solana_account_address, - string_setter_contract.solana_address]) diff --git a/evm_loader/tests/utils/__init__.py b/evm_loader/tests/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/evm_loader/tests/utils/assert_messages.py b/evm_loader/tests/utils/assert_messages.py deleted file mode 100644 index 39a69e9aa..000000000 --- a/evm_loader/tests/utils/assert_messages.py +++ /dev/null @@ -1,17 +0,0 @@ -class InstructionAsserts: - LOCKED_ACC = "trying to execute transaction on rw locked account" - INVALID_CHAIN_ID = "Invalid Chain ID" - INVALID_NONCE = "Invalid Nonce" - TRX_ALREADY_FINALIZED = "is finalized" - INSUFFICIENT_FUNDS = "Insufficient balance" - OUT_OF_GAS = "Out of Gas" - ADDRESS_MUST_BE_PRESENT = r"address .* must be present in the transaction" - INVALID_TREASURE_ACC = "invalid treasure account" - ACC_NOT_FOUND = "AccountNotFound" - NOT_AUTHORIZED_OPERATOR = "Operator is not authorized" - NOT_SYSTEM_PROGRAM = "Account {} - is not system program" - NOT_NEON_PROGRAM = "Account {} - is not Neon program" - NOT_PROGRAM_OWNED = "Account {} - invalid owner" - INVALID_HOLDER_OWNER = "Holder Account - invalid owner" - INVALID_OPERATOR_KEY = "operator.key != storage.operator" - HOLDER_OVERFLOW = "Checked Integer Math Overflow" diff --git a/evm_loader/tests/utils/constants.py b/evm_loader/tests/utils/constants.py deleted file mode 100644 index 0e78ae976..000000000 --- a/evm_loader/tests/utils/constants.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -from solana.publickey import PublicKey -from construct import Bytes, Int8ul, Struct as cStruct - - -SYSTEM_ADDRESS = "11111111111111111111111111111111" -TOKEN_KEG_ADDRESS = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" -SYSVAR_CLOCK_ADDRESS = "SysvarC1ock11111111111111111111111111111111" -SYS_INSTRUCT_ADDRESS = "Sysvar1nstructions1111111111111111111111111" -KECCAKPROG_ADDRESS = "KeccakSecp256k11111111111111111111111111111" -RENT_ID_ADDRESS = "SysvarRent111111111111111111111111111111111" -INCINERATOR_ADDRESS = "1nc1nerator11111111111111111111111111111111" -TREASURY_POOL_SEED = os.environ.get("NEON_POOL_SEED", "treasury_pool") -TREASURY_POOL_COUNT = os.environ.get("NEON_POOL_COUNT", 128) -COMPUTE_BUDGET_ID: PublicKey = PublicKey("ComputeBudget111111111111111111111111111111") - -ACCOUNT_SEED_VERSION = b'\3' - -TAG_EMPTY = 0 -TAG_ACCOUNT_V3 = 12 -TAG_STATE = 22 -TAG_FINALIZED_STATE = 31 -TAG_CONTRACT_STORAGE = 42 -TAG_HOLDER = 51 - -SOLANA_URL = os.environ.get("SOLANA_URL", "http://localhost:8899") -EVM_LOADER = os.environ.get("EVM_LOADER") -NEON_TOKEN_MINT_ID: PublicKey = PublicKey(os.environ.get("NEON_TOKEN_MINT")) diff --git a/evm_loader/tests/utils/contract.py b/evm_loader/tests/utils/contract.py deleted file mode 100644 index d01d0bbcf..000000000 --- a/evm_loader/tests/utils/contract.py +++ /dev/null @@ -1,95 +0,0 @@ -import typing as tp -import pathlib - -import eth_abi -import pytest -from eth_account.datastructures import SignedTransaction -from eth_utils import abi -from solana.keypair import Keypair - -from .types import Caller, TreasuryPool -from ..solana_utils import solana_client, \ - get_transaction_count, EvmLoader, write_transaction_to_holder_account, \ - send_transaction_step_from_account, neon_cli -from .storage import create_holder -from .ethereum import create_contract_address, make_eth_transaction - -from web3.auto import w3 - - -def make_deployment_transaction( - user: Caller, - contract_path: tp.Union[pathlib.Path, str], - encoded_args=None, - gas: int = 999999999, chain_id=111 -) -> SignedTransaction: - if isinstance(contract_path, str): - contract_path = pathlib.Path(contract_path) - if not contract_path.name.startswith("/") or not contract_path.name.startswith("."): - contract_path = pytest.CONTRACTS_PATH / contract_path - with open(contract_path, 'rb') as f: - data = f.read() - - if encoded_args is not None: - data += encoded_args - - tx = { - 'to': None, - 'value': 0, - 'gas': gas, - 'gasPrice': 0, - 'nonce': get_transaction_count(solana_client, user.solana_account_address), - 'data': data - } - if chain_id is not None: - tx['chainId'] = chain_id - print(tx) - return w3.eth.account.sign_transaction(tx, user.solana_account.secret_key[:32]) - - -def make_contract_call_trx(user, contract, function_signature, params=None, value=0, chain_id=111): - data = abi.function_signature_to_4byte_selector(function_signature) - - if params is not None: - for param in params: - if isinstance(param, int): - data += eth_abi.encode(['uint256'], [param]) - elif isinstance(param, str): - data += eth_abi.encode(['string'], [param]) - - signed_tx = make_eth_transaction(contract.eth_address, data, user.solana_account, user.solana_account_address, - value=value, chain_id=chain_id) - return signed_tx - - -def deploy_contract( - operator: Keypair, - user: Caller, - contract_path: tp.Union[pathlib.Path, str], - evm_loader: EvmLoader, - treasury_pool: TreasuryPool, - step_count: int = 1000, - encoded_args=None -): - print("Deploying contract") - if isinstance(contract_path, str): - contract_path = pathlib.Path(contract_path) - contract = create_contract_address(user, evm_loader) - holder_acc = create_holder(operator) - signed_tx = make_deployment_transaction(user, contract_path, encoded_args=encoded_args) - write_transaction_to_holder_account(signed_tx, holder_acc, operator) - - contract_deployed = False - while not contract_deployed: - receipt = send_transaction_step_from_account(operator, evm_loader, treasury_pool, holder_acc, - [contract.solana_address, user.solana_account_address], - step_count, operator) - if receipt.value.transaction.meta.err: - raise AssertionError(f"Can't deploy contract: {receipt.value.transaction.meta.err}") - for log in receipt.value.transaction.meta.log_messages: - if "exit_status" in log: - contract_deployed = True - break - if "ExitError" in log: - raise AssertionError(f"EVM Return error in logs: {receipt}") - return contract diff --git a/evm_loader/tests/utils/ethereum.py b/evm_loader/tests/utils/ethereum.py deleted file mode 100644 index cb3b01a7b..000000000 --- a/evm_loader/tests/utils/ethereum.py +++ /dev/null @@ -1,46 +0,0 @@ -from dataclasses import dataclass -from typing import Union - -from base58 import b58encode -from sha3 import keccak_256 -from solana.keypair import Keypair -from solana.publickey import PublicKey -from web3.auto import w3 - -from .constants import ACCOUNT_SEED_VERSION -from .types import Caller, Contract -from ..eth_tx_utils import pack -from ..solana_utils import EvmLoader, solana_client, get_transaction_count - - - - -def create_contract_address(user: Caller, evm_loader: EvmLoader) -> Contract: - # Create contract address from (caller_address, nonce) - user_nonce = get_transaction_count(solana_client, user.solana_account_address) - contract_eth_address = keccak_256(pack([user.eth_address, user_nonce or None])).digest()[-20:] - contract_solana_address, contract_nonce = evm_loader.ether2program(contract_eth_address) - seed = b58encode(ACCOUNT_SEED_VERSION + contract_eth_address).decode('utf8') - print(f"Contract addresses: " - f" eth {contract_eth_address.hex()}, " - f" solana {contract_solana_address}" - f" nonce {contract_nonce}" - f" user nonce {user_nonce}" - f" seed {seed}") - return Contract(contract_eth_address, PublicKey(contract_solana_address), contract_nonce, seed) - - -def make_eth_transaction(to_addr: bytes, data: Union[bytes, None], signer: Keypair, from_solana_user: PublicKey, - value: int = 0, chain_id = 111, gas = 9999999999): - nonce = get_transaction_count(solana_client, from_solana_user) - tx = {'to': to_addr, 'value': value, 'gas': gas, 'gasPrice': 0, - 'nonce': nonce} - - if chain_id is not None: - tx['chainId'] = chain_id - - if data is not None: - tx['data'] = data - - return w3.eth.account.sign_transaction(tx, signer.secret_key[:32]) - diff --git a/evm_loader/tests/utils/instructions.py b/evm_loader/tests/utils/instructions.py deleted file mode 100644 index ffdf9b78b..000000000 --- a/evm_loader/tests/utils/instructions.py +++ /dev/null @@ -1,218 +0,0 @@ -import typing as tp - -from eth_keys import keys as eth_keys -from solana.keypair import Keypair -from solana.publickey import PublicKey -import solana.system_program as sp -from solana.transaction import AccountMeta, TransactionInstruction, Transaction - -from .constants import EVM_LOADER, INCINERATOR_ADDRESS - -DEFAULT_UNITS = 500 * 1000 -DEFAULT_HEAP_FRAME = 256 * 1024 -DEFAULT_ADDITIONAL_FEE = 0 -COMPUTE_BUDGET_ID: PublicKey = PublicKey("ComputeBudget111111111111111111111111111111") - - -class ComputeBudget: - @staticmethod - def request_units(operator, units, additional_fee): - return TransactionInstruction( - program_id=COMPUTE_BUDGET_ID, - keys=[AccountMeta(PublicKey(operator.public_key), is_signer=True, is_writable=False)], - data=bytes.fromhex("02") + units.to_bytes(4, "little") # + additional_fee.to_bytes(4, "little") - ) - - @staticmethod - def request_heap_frame(operator, heap_frame): - return TransactionInstruction( - program_id=COMPUTE_BUDGET_ID, - keys=[AccountMeta(PublicKey(operator.public_key), is_signer=True, is_writable=False)], - data=bytes.fromhex("01") + heap_frame.to_bytes(4, "little") - ) - - -class TransactionWithComputeBudget(Transaction): - def __init__(self, - operator: Keypair, - units=DEFAULT_UNITS, - additional_fee=DEFAULT_ADDITIONAL_FEE, - heap_frame=DEFAULT_HEAP_FRAME, - *args, **kwargs): - super().__init__(*args, **kwargs) - if units: - self.add(ComputeBudget.request_units(operator, units, additional_fee)) - if heap_frame: - self.add(ComputeBudget.request_heap_frame(operator, heap_frame)) - - -def write_holder_layout(hash: bytes, offset: int, data: bytes): - assert (len(hash) == 32) - return ( - bytes.fromhex("26") - + hash - + offset.to_bytes(8, byteorder="little") - + data - ) - - -def make_WriteHolder(operator: PublicKey, holder_account: PublicKey, hash: bytes, offset: int, payload: bytes): - d = write_holder_layout(hash, offset, payload) - - return TransactionInstruction( - program_id=PublicKey(EVM_LOADER), - data=d, - keys=[ - AccountMeta(pubkey=holder_account, is_signer=False, is_writable=True), - AccountMeta(pubkey=operator, is_signer=True, is_writable=False), - ]) - - -def make_ExecuteTrxFromInstruction( - operator: Keypair, - evm_loader: "EvmLoader", - treasury_address: PublicKey, - treasury_buffer: bytes, - message: bytes, - additional_accounts: tp.List[PublicKey], - system_program=sp.SYS_PROGRAM_ID, - evm_loader_public_key=PublicKey(EVM_LOADER) -): - data = bytes.fromhex('1f') + treasury_buffer + message - operator_ether = eth_keys.PrivateKey(operator.secret_key[:32]).public_key.to_canonical_address() - print("make_ExecuteTrxFromInstruction accounts") - print("Operator: ", operator.public_key) - print("Treasury: ", treasury_address) - print("Operator ether: ", operator_ether.hex()) - print("Operator eth solana: ", evm_loader.ether2program(operator_ether)[0]) - accounts = [ - AccountMeta(pubkey=operator.public_key, is_signer=True, is_writable=True), - AccountMeta(pubkey=treasury_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=PublicKey(evm_loader.ether2program(operator_ether)[0]), is_signer=False, is_writable=True), - AccountMeta(system_program, is_signer=False, is_writable=True), - AccountMeta(evm_loader_public_key, is_signer=False, is_writable=False), - ] - for acc in additional_accounts: - print("Additional acc ", acc) - accounts.append(AccountMeta(acc, is_signer=False, is_writable=True), ) - - return TransactionInstruction( - program_id=PublicKey(EVM_LOADER), - data=data, - keys=accounts - ) - - -def make_ExecuteTrxFromAccountDataIterativeOrContinue( - operator: Keypair, - evm_loader: "EvmLoader", - holder_address: PublicKey, - treasury_address: PublicKey, - treasury_buffer: bytes, - step_count: int, - additional_accounts: tp.List[PublicKey], - sys_program_id=sp.SYS_PROGRAM_ID, - neon_evm_program_id=PublicKey(EVM_LOADER), tag=33): - # 33 - TransactionStepFromAccount - # 34 - TransactionStepFromAccountNoChainId - d = tag.to_bytes(1, "little") + treasury_buffer + step_count.to_bytes(8, byteorder="little") - operator_ether = eth_keys.PrivateKey(operator.secret_key[:32]).public_key.to_canonical_address() - print("make_ExecuteTrxFromAccountDataIterativeOrContinue accounts") - print("Holder: ", holder_address) - print("Operator: ", operator.public_key) - print("Treasury: ", treasury_address) - print("Operator ether: ", operator_ether.hex()) - print("Operator eth solana: ", evm_loader.ether2program(operator_ether)[0]) - accounts = [ - AccountMeta(pubkey=holder_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=operator.public_key, is_signer=True, is_writable=True), - AccountMeta(pubkey=treasury_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=PublicKey(evm_loader.ether2program(operator_ether)[0]), is_signer=False, is_writable=True), - AccountMeta(sys_program_id, is_signer=False, is_writable=True), - # Neon EVM account - AccountMeta(neon_evm_program_id, is_signer=False, is_writable=False), - ] - - for acc in additional_accounts: - print("Additional acc ", acc) - accounts.append(AccountMeta(acc, is_signer=False, is_writable=True), ) - - return TransactionInstruction( - program_id=PublicKey(EVM_LOADER), - data=d, - keys=accounts - ) - - -def make_PartialCallOrContinueFromRawEthereumTX( - instruction: bytes, - operator: Keypair, - evm_loader: "EvmLoader", - storage_address: PublicKey, - treasury_address: PublicKey, - treasury_buffer: bytes, - step_count: int, - additional_accounts: tp.List[PublicKey], - system_program=sp.SYS_PROGRAM_ID, - evm_loader_public_key=PublicKey(EVM_LOADER)): - d = (32).to_bytes(1, "little") + treasury_buffer + step_count.to_bytes(8, byteorder="little") + instruction - operator_ether = eth_keys.PrivateKey(operator.secret_key[:32]).public_key.to_canonical_address() - - accounts = [ - AccountMeta(pubkey=storage_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=operator.public_key, is_signer=True, is_writable=True), - AccountMeta(pubkey=treasury_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=PublicKey(evm_loader.ether2program(operator_ether)[0]), is_signer=False, is_writable=True), - AccountMeta(system_program, is_signer=False, is_writable=True), - # Neon EVM account - AccountMeta(evm_loader_public_key, is_signer=False, is_writable=False), - ] - for acc in additional_accounts: - accounts.append(AccountMeta(acc, is_signer=False, is_writable=True), ) - - return TransactionInstruction( - program_id=PublicKey(EVM_LOADER), - data=d, - keys=accounts - ) - - -def make_Cancel(storage_address: PublicKey, operator: Keypair, hash: bytes, additional_accounts: tp.List[PublicKey]): - d = (35).to_bytes(1, "little") + hash - - accounts = [ - AccountMeta(pubkey=storage_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=operator.public_key, is_signer=True, is_writable=True), - AccountMeta(pubkey=PublicKey(INCINERATOR_ADDRESS), is_signer=False, is_writable=True), - ] - - for acc in additional_accounts: - accounts.append(AccountMeta(acc, is_signer=False, is_writable=True), ) - - return TransactionInstruction( - program_id=PublicKey(EVM_LOADER), - data=d, - keys=accounts - ) - - -def make_DepositV03( - ether_address: bytes, - solana_account: PublicKey, - source: PublicKey, - pool: PublicKey, - token_program: PublicKey, - operator_pubkey: PublicKey, -) -> TransactionInstruction: - data = bytes.fromhex('27') + ether_address - - accounts = [ - AccountMeta(pubkey=source, is_signer=False, is_writable=True), - AccountMeta(pubkey=pool, is_signer=False, is_writable=True), - AccountMeta(pubkey=solana_account, is_signer=False, is_writable=True), - AccountMeta(pubkey=token_program, is_signer=False, is_writable=False), - AccountMeta(pubkey=operator_pubkey, is_signer=True, is_writable=True), - AccountMeta(pubkey=sp.SYS_PROGRAM_ID, is_signer=False, is_writable=False), - ] - - return TransactionInstruction(program_id=PublicKey(EVM_LOADER), data=data, keys=accounts) diff --git a/evm_loader/tests/utils/layouts.py b/evm_loader/tests/utils/layouts.py deleted file mode 100644 index 96046c186..000000000 --- a/evm_loader/tests/utils/layouts.py +++ /dev/null @@ -1,45 +0,0 @@ -from construct import Bytes, Int8ul, Struct, Int64ul, Int32ul - -STORAGE_ACCOUNT_INFO_LAYOUT = Struct( - "tag" / Int8ul, - "owner" / Bytes(32), - "hash" / Bytes(32), - "caller" / Bytes(20), - "gas_limit" / Bytes(32), - "gas_price" / Bytes(32), - "gas_used" / Bytes(32), - "operator" / Bytes(32), - "slot" / Int64ul, - "account_list_len" / Int64ul, -) - -HOLDER_ACCOUNT_INFO_LAYOUT = Struct( - "tag" / Int8ul, - "owner" / Bytes(32), - "hash" / Bytes(32), - "len" / Int64ul -) - - -FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT = Struct( - "tag" / Int8ul, - "owner" / Bytes(32), - "hash" / Bytes(32), -) - - -ACCOUNT_INFO_LAYOUT = Struct( - "type" / Int8ul, - "ether" / Bytes(20), - "nonce" / Int8ul, - "trx_count" / Bytes(8), - "balance" / Bytes(32), - "generation" / Int32ul, - "code_size" / Int32ul, - "is_rw_blocked" / Int8ul, -) - - -CREATE_ACCOUNT_LAYOUT = Struct( - "ether" / Bytes(20), -) diff --git a/evm_loader/tests/utils/storage.py b/evm_loader/tests/utils/storage.py deleted file mode 100644 index 5298a5ecc..000000000 --- a/evm_loader/tests/utils/storage.py +++ /dev/null @@ -1,47 +0,0 @@ -from hashlib import sha256 -from random import randrange - -from solana.publickey import PublicKey -from solana.keypair import Keypair -from ..solana_utils import create_holder_account, get_solana_balance, create_account_with_seed, \ - send_transaction, solana_client -from solana.transaction import Transaction, TransactionInstruction, AccountMeta -from .constants import EVM_LOADER - - -def create_holder(signer: Keypair, seed: str = None, size: int = None, fund: int = None, - storage: PublicKey = None) -> PublicKey: - if size is None: - size = 128 * 1024 - if fund is None: - fund = 10 ** 9 - if seed is None: - seed = str(randrange(1000000)) - if storage is None: - storage = PublicKey( - sha256(bytes(signer.public_key) + bytes(seed, 'utf8') + bytes(PublicKey(EVM_LOADER))).digest()) - - print(f"Create holder account with seed: {seed}") - - if get_solana_balance(storage) == 0: - trx = Transaction() - trx.add( - create_account_with_seed(signer.public_key, signer.public_key, seed, fund, size), - create_holder_account(storage, signer.public_key) - ) - send_transaction(solana_client, trx, signer) - print(f"Created holder account: {storage}") - return storage - - -def delete_holder(del_key: PublicKey, acc: Keypair, signer: Keypair): - trx = Transaction() - - trx.add(TransactionInstruction( - program_id=PublicKey(EVM_LOADER), - data=bytes.fromhex("25"), - keys=[ - AccountMeta(pubkey=del_key, is_signer=False, is_writable=True), - AccountMeta(pubkey=acc.public_key, is_signer=(signer == acc), is_writable=True), - ])) - return send_transaction(solana_client, trx, signer) diff --git a/evm_loader/tests/utils/transaction_checks.py b/evm_loader/tests/utils/transaction_checks.py deleted file mode 100644 index 5292426fa..000000000 --- a/evm_loader/tests/utils/transaction_checks.py +++ /dev/null @@ -1,28 +0,0 @@ -import base64 - -from solana.rpc.commitment import Confirmed - -from ..solana_utils import solana_client - - -def check_transaction_logs_have_text(trx_hash, text): - - receipt = solana_client.get_transaction(trx_hash) - logs = "" - for log in receipt.value.transaction.meta.log_messages: - if "Program data:" in log: - logs += "Program data: " - encoded_part = log.replace("Program data: ", "") - for item in encoded_part.split(" "): - logs += " " + str(base64.b64decode(item)) - else: - logs += log - logs += " " - assert text in logs, f"Transaction logs don't contain '{text}'. Logs: {logs}" - - -def check_holder_account_tag(storage_account, layout, expected_tag): - account_data = solana_client.get_account_info(storage_account, commitment=Confirmed).value.data - parsed_data = layout.parse(account_data) - assert parsed_data.tag == expected_tag, f"Account data {account_data} doesn't contain tag {expected_tag}" - diff --git a/evm_loader/tests/utils/types.py b/evm_loader/tests/utils/types.py deleted file mode 100644 index 9e07cdecf..000000000 --- a/evm_loader/tests/utils/types.py +++ /dev/null @@ -1,27 +0,0 @@ -from dataclasses import dataclass -from solana.publickey import PublicKey -from solana.keypair import Keypair - - -@dataclass -class TreasuryPool: - index: int - account: PublicKey - buffer: bytes - - -@dataclass -class Caller: - solana_account: Keypair - solana_account_address: PublicKey - eth_address: bytes - nonce: int - token_address: PublicKey - - -@dataclass -class Contract: - eth_address: bytes - solana_address: PublicKey - nonce: int - seed: str diff --git a/evm_loader/wait-for-neon.sh b/evm_loader/wait-for-neon.sh deleted file mode 100755 index f3d2da8aa..000000000 --- a/evm_loader/wait-for-neon.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -set -euo pipefail - -: ${EVM_LOADER:=$(solana address -k evm_loader-keypair.json)} -: ${SOLANA_URL:?is not set} - -./wait-for-solana.sh "$@" - -if [ $# -eq 0 ]; then - if neon-cli --url $SOLANA_URL --evm_loader $EVM_LOADER --loglevel off init-environment > /dev/null; then - exit 0 - fi -else - WAIT_TIME=$1 - echo "Waiting $WAIT_TIME seconds for NeonEVM to be available at $SOLANA_URL:$EVM_LOADER" - for i in $(seq 1 $WAIT_TIME); do - if neon-cli --url $SOLANA_URL --evm_loader $EVM_LOADER --loglevel off init-environment > /dev/null; then - exit 0 - fi - sleep 1 - done -fi - -echo "unable to connect to NeonEVM at $SOLANA_URL:$EVM_LOADER" -exit 1 \ No newline at end of file diff --git a/evm_loader/wait-for-solana.sh b/evm_loader/wait-for-solana.sh deleted file mode 100755 index 072911fa4..000000000 --- a/evm_loader/wait-for-solana.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -euo pipefail - -: ${SOLANA_URL:?is not set} - -if [ $# -eq 0 ]; then - if solana -u $SOLANA_URL cluster-version >/dev/null 2>&1; then exit 0; fi -else - WAIT_TIME=$1 - echo "Waiting $WAIT_TIME seconds for solana cluster to be available at $SOLANA_URL" - for i in $(seq 1 $WAIT_TIME); do - if solana -u $SOLANA_URL cluster-version >/dev/null 2>&1; then exit 0; fi - sleep 1 - done -fi - -echo "unable to connect to solana cluster $SOLANA_URL" -exit 1 - - diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 10add70bb..7897a24d1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.64.0" +channel = "1.75.0" diff --git a/evm_loader/solidity/Metaplex.sol b/solidity/Metaplex.sol similarity index 100% rename from evm_loader/solidity/Metaplex.sol rename to solidity/Metaplex.sol diff --git a/evm_loader/solidity/SPLToken.sol b/solidity/SPLToken.sol similarity index 100% rename from evm_loader/solidity/SPLToken.sol rename to solidity/SPLToken.sol diff --git a/evm_loader/solidity/big_contract.sol b/solidity/big_contract.sol similarity index 100% rename from evm_loader/solidity/big_contract.sol rename to solidity/big_contract.sol diff --git a/solidity/call_solana.sol b/solidity/call_solana.sol new file mode 100644 index 000000000..96539d035 --- /dev/null +++ b/solidity/call_solana.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >= 0.7.0; +pragma abicoder v2; + +interface CallSolana { + + struct Instruction { + bytes32 program_id; + AccountMeta[] accounts; + bytes instruction_data; + } + + struct AccountMeta { + bytes32 account; + bool is_signer; + bool is_writable; + } + + // Returns Solana address for Neon address. + // Calculates as PDA([ACCOUNT_SEED_VERSION, Neon-address], evm_loader_id) + function getNeonAddress(address) external view returns (bytes32); + + + // Returns Solana address of resource for contracts. + // Calculates as PDA([ACCONT_SEED_VERSION, "ContractData", msg.sender, salt], evm_loader_id) + function getResourceAddress(bytes32 salt) external view returns (bytes32); + + + // Creates resource with specified salt. + // Return the Solana address of the created resource (see `getResourceAddress`) + function createResource(bytes32 salt, uint64 space, uint64 lamports, bytes32 owner) external returns (bytes32); + + + // Returns Solana PDA generated from specified program_id and seeds + function getSolanaPDA(bytes32 program_id, bytes memory seeds) external view returns (bytes32); + + + // Returns Solana address of the external authority. + // Calculates as PDA([ACCOUNT_SEED_VERSION, "AUTH", msg.sender, salt], evm_loader_id) + function getExtAuthority(bytes32 salt) external view returns (bytes32); + + + // Return Solana address for payer account (if instruction required some account to funding new created accounts) + // Calculates as PDA([ACCOUNT_SEED_VERSION, "PAYER", msg.sender], evm_loader_id) + function getPayer() external view returns (bytes32); + + + // Execute the instruction with a call to the Solana program. + // Guarantees successful execution of call after a success return. + // Note: If the call was unsuccessful, the transaction fails (due to Solana's behaviour). + // The `lamports` parameter specifies the amount of lamports that can be required to create new accounts during execution. + // This lamports transferred to `payer`-account (see `getPayer()` function) before the call. + // - `instruction` - instruction which should be executed + // This method uses PDA for sender to authorize the operation (`getNeonAddress(msg.sender)`) + // Returns the returned data of the executed instruction (if program returned the data is equal to the program_id of the instruction) + function execute(uint64 lamports, Instruction memory instruction) external returns (bytes memory); + + + // Execute the instruction with call to the Solana program. + // Guarantees successful execution of call after a success return. + // Note: If the call was unsuccessful, the transaction fails (due to Solana's behaviour). + // The `lamports` parameter specifies the amount of lamports that can be required to create new accounts during execution. + // This lamports transferred to `payer`-account (see `getPayer()` function) before the call. + // - `salt` - the salt to generate an address of external authority (see `getExtAuthority()` function) + // - `instruction` - instruction which should be executed + // This method uses external authority to authorize the operation (`getExtAuthority(salt)`) + // Returns the returned data of the executed instruction (if program returned the data is equal to the program_id of the instruction) + function executeWithSeed(uint64 lamports, bytes32 salt, Instruction memory instruction) external returns (bytes memory); + + + // Execute the instruction with a call to the Solana program. + // Guarantees successful execution of call after a success return. + // Note: If the call was unsuccessful, the transaction fails (due to Solana's behaviour). + // The `lamports` parameter specifies the amount of lamports that can be required to create new accounts during execution. + // This lamports transferred to `payer`-account (see `getPayer()` function) before the call. + // - `instruction` - bincode serialized instruction which should be executed + // This method uses PDA for sender to authorize the operation (`getNeonAddress(msg.sender)`) + // Returns the returned data of the executed instruction (if program returned the data is equal to the program_id of the instruction) + function execute(uint64 lamports, bytes memory instruction) external returns (bytes memory); + + + // Execute the instruction with call to the Solana program. + // Guarantees successful execution of call after a success return. + // Note: If the call was unsuccessful, the transaction fails (due to Solana's behaviour). + // The `lamports` parameter specifies the amount of lamports that can be required to create new accounts during execution. + // This lamports transferred to `payer`-account (see `getPayer()` function) before the call. + // - `salt` - the salt to generate an address of external authority (see `getExtAuthority()` function) + // - `instruction` - bincode serialized instruction which should be executed + // This method uses external authority to authorize the operation (`getExtAuthority(salt)`) + // Returns the returned data of the executed instruction (if program returned the data is equal to the program_id of the instruction) + function executeWithSeed(uint64 lamports, bytes32 salt, bytes memory instruction) external returns (bytes memory); + + + // Returns the program_id and returned data of the last executed instruction (if no return data was set returns zeroed bytes) + // For more information see: https://docs.rs/solana-program/latest/solana_program/program/fn.get_return_data.html + // Note: This method should be called after a call to `execute`/`executeWithSeed` methods + function getReturnData() external view returns (bytes32, bytes memory); +} + + +/* Note: + For `execute`/`executeWithSeed` methods which gets instruction in bincode serialized format + the instruction should be serialized according to Solana bincode serialize rules. It requires + serialized data in the following form: + + program_id as bytes32 + len(accounts) as uint64le + account as bytes32 + is_signer as bool + is_writable as bool + len(data) as uint64le + data (see instruction to Solana program) + +The optimized way to serailize instruction is write this code on the solidity assembler. +To perform a call to `execute()` and `executeWithSeed()` methods the next code-sample can be helpful: +```solidity + { + // TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA + bytes32 program_id = 0x06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9; + bytes32 owner = getNeonAddress(address(this)); + + bytes4 selector = bytes4(keccak256("execute(uint64,bytes)")); + bool success; + assembly { + let buff := mload(0x40) // the head of heap + let pos := buff // current write position + + // selector + mstore(pos, selector) // write the method selector + pos := add(pos, 4) + + // Write arguments to call the method + // lamports + mstore(pos, 0) // write required lamports + pos := add(pos, 32) + + // offset for instruction + // specify the position of serialized instruction relative to start of arguments + mstore(pos, sub(add(pos, 28), buff)) + pos := add(pos, 32) + let size_pos := pos // Save size position of serialized instruction + pos := add(pos, 32) + + // program_id + mstore(pos, program_id) + pos := add(pos, 32) + + // len(accounts) + mstore(pos, 0) + mstore8(pos, 4) + pos := add(pos, 8) + + // For each account in accounts array: + // AccountMeta(resource, false, true) + mstore(pos, owner) // pubkey + mstore8(add(pos, 32), 1) // is_signer + mstore8(add(pos, 33), 0) // is_writable + pos := add(pos, 34) + + // len(instruction_data) if it shorter than 256 bytes + mstore(pos, 0) // fill with zero next 32 bytes + mstore8(pos, 1) // write the length of data + pos := add(pos, 8) + + // instruction_data: InitializeAccount + mstore8(pos, 1) // Use Solana program instruction to detailed info + pos := add(pos, 1) + + mstore(size_pos, sub(sub(pos, size_pos), 32)) // write the size of serialized instruction + let length := sub(pos, buff) // calculate the length of arguments + mstore(0x40, pos) // update head of heap + success := call(5000, 0xFF00000000000000000000000000000000000006, 0, buff, length, buff, 0x20) + mstore(0x40, buff) // restore head of heap + } + if (success == false) { + revert("Can't execute instruction"); + } + } +``` +*/ diff --git a/solidity/call_solana_test.sol b/solidity/call_solana_test.sol new file mode 100644 index 000000000..ea3f45b6d --- /dev/null +++ b/solidity/call_solana_test.sol @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >= 0.7.0; +pragma abicoder v2; +import './SPLToken.sol'; +import './call_solana.sol'; + +SPLToken constant _splToken = SPLToken(0xFf00000000000000000000000000000000000004); +CallSolana constant _callSolana = CallSolana(0xFF00000000000000000000000000000000000006); + +contract Test { + uint256 balance; + + function reverse(uint64 input) internal pure returns (uint64 v) { + v = input; + + // swap bytes + v = ((v & 0xFF00FF00FF00FF00) >> 8) | + ((v & 0x00FF00FF00FF00FF) << 8); + + // swap 2-byte long pairs + v = ((v & 0xFFFF0000FFFF0000) >> 16) | + ((v & 0x0000FFFF0000FFFF) << 16); + + // swap 4-byte long pairs + v = (v >> 32) | (v << 32); + } + + function getNeonAddress(address addr) internal returns (bytes32 owner) { + bytes4 selector = bytes4(keccak256("getNeonAddress(address)")); + assembly { + let buff := mload(0x40) + let pos := buff + + mstore(pos, selector) + pos := add(pos, 4) + + mstore(pos, addr) + pos := add(pos, 32) + + let length := sub(pos, buff) + mstore(0x40, pos) + + let success := call(5000, 0xFF00000000000000000000000000000000000006, 0, buff, length, buff, 0x20) + owner := mload(buff) + + mstore(0x40, buff) + } + } + + constructor() { + // TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA + bytes32 program_id = 0x06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9; + + // bytes32 d = 0x6134f00584594e09f9f664be8195ce25deb2a92e3022ef6e5b1d9ad9c2a03a33; + // bytes memory data = abi.encodePacked(d, true, false); + + bytes32 salt = _salt(msg.sender); + + // //bytes32 resource = _callSolana.getResourceAddress(0x00); + bytes32 resource = _callSolana.createResource(salt, 165, 6960*165, program_id); // FwBENPdrTaxftBCnvXNDAyMAk4yYgZjhwqd7xvDNEDpG + bytes32 mint = 0xf396da383e57418540f8caa598584f49a3b50d256f75cb6d94d101681d6d9d21; // HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU + //bytes32 owner = _callSolana.getNeonAddress(address(this)); // 4rcXs8Z5PJCdiR6nj6G8BiGCwsf16wseYCyE54o7t7UA + bytes32 owner = getNeonAddress(address(this)); + bytes32 rent = 0x06a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a00000000; // SysvarRent111111111111111111111111111111111 + + { // Call SPLToken::InitailizeAccount() for resource,mint,owner,rent accounts + bytes4 selector = bytes4(keccak256("execute(uint64,bytes)")); + bool success; + assembly { + let buff := mload(0x40) // the head of heap + let pos := buff + + // selector + mstore(pos, selector) + pos := add(pos, 4) + + // required_lamports + mstore(pos, 0) + pos := add(pos, 32) + + // offset for instruction + mstore(pos, sub(add(pos, 28), buff)) + pos := add(pos, 32) + let size_pos := pos + pos := add(pos, 32) + + // program_id + mstore(pos, program_id) + pos := add(pos, 32) + + // len(accounts) + mstore(pos, 0) + mstore8(pos, 4) + pos := add(pos, 8) + + // AccountMeta(resource, false, true) + mstore(pos, resource) + mstore8(add(pos, 32), 0) + mstore8(add(pos, 33), 1) + pos := add(pos, 34) + + // AccountMeta(mint, false, false) + mstore(pos, mint) + mstore8(add(pos, 32), 0) + mstore8(add(pos, 33), 0) + pos := add(pos, 34) + + // AccountMeta(owner, false, false) + mstore(pos, owner) + mstore8(add(pos, 32), 0) + mstore8(add(pos, 33), 0) + pos := add(pos, 34) + + // AccountMeta(rent, false, false) + mstore(pos, rent) + mstore8(add(pos, 32), 0) + mstore8(add(pos, 33), 0) + pos := add(pos, 34) + + // len(instruction_data) + mstore(pos, 0) + mstore8(pos, 1) + pos := add(pos, 8) + + // instruction_data: InitializeAccount + mstore8(pos, 1) + pos := add(pos, 1) + + mstore(size_pos, sub(sub(pos, size_pos), 32)) + let length := sub(pos, buff) + mstore(0x40, pos) + success := call(5000, 0xFF00000000000000000000000000000000000006, 0, buff, length, buff, 0x20) + mstore(0x40, buff) + } + if (success == false) { + revert("Can't initailize resource"); + } + } + + bytes32 source = 0xf567a23ec5b9f6a543bb3fd8d5ab7f2d4682fcb1908b755b477f116ec2925af8; // HWxYLBFfPxzrtGTKZL3VEN9dTDBz7qDgFXJVcw8zFkfq + { + bytes4 selector = bytes4(keccak256("execute(uint64,bytes)")); + uint amount = uint(reverse(1000)) << (256-64); + bool success; + assembly { + let buff := mload(0x40) + let pos := buff + + // selector + mstore(pos, selector) + pos := add(pos, 4) + + // required_lamports + mstore(pos, 0) + pos := add(pos, 32) + + // offset for instruction + mstore(pos, sub(add(pos, 28), buff)) + pos := add(pos, 32) + let size_pos := pos + pos := add(pos, 32) + + // program_id + mstore(pos, program_id) + pos := add(pos, 32) + + // len(accounts) + mstore(pos, 0) + mstore8(pos, 3) + pos := add(pos, 8) + + // AccountMeta(resource, false, true) + mstore(pos, source) + mstore8(add(pos, 32), 0) + mstore8(add(pos, 33), 1) + pos := add(pos, 34) + + // AccountMeta(mint, false, false) + mstore(pos, resource) + mstore8(add(pos, 32), 0) + mstore8(add(pos, 33), 1) + pos := add(pos, 34) + + // AccountMeta(owner, false, false) + mstore(pos, owner) + mstore8(add(pos, 32), 1) + mstore8(add(pos, 33), 0) + pos := add(pos, 34) + + // len(instruction_data) + mstore(pos, 0) + mstore8(pos, 9) + pos := add(pos, 8) + + // instruction_data: Transfer + mstore8(pos, 3) + mstore(add(pos, 1), amount) + pos := add(pos, 9) + + mstore(size_pos, sub(sub(pos, size_pos), 32)) + let length := sub(pos, buff) + mstore(0x40, pos) + success := call(5000, 0xFF00000000000000000000000000000000000006, 0, buff, length, buff, 0x20) + mstore(0x40, buff) + } + if (success == false) { + revert("Can't initailize resource"); + } + } + + balance = balanceOf(msg.sender); + } + + function balanceOf(address who) public view returns (uint256) { + bytes32 account = _solanaAccount(who); + return _splToken.getAccount(account).amount; + } + + function _salt(address account) internal pure returns (bytes32) { + return bytes32(uint256(uint160(account))); + } + + function _solanaAccount(address account) internal pure returns (bytes32) { + return _splToken.findAccount(_salt(account)); + } +} + + +/* +solana airdrop 1 +spl-token create-account HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU +spl-token mint HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU 1000000 HWxYLBFfPxzrtGTKZL3VEN9dTDBz7qDgFXJVcw8zFkfq --mint-authority ~/neonlabs/neon-evm.git/ci/evm_loader-keypair.json +spl-token approve HWxYLBFfPxzrtGTKZL3VEN9dTDBz7qDgFXJVcw8zFkfq 1000000 4rcXs8Z5PJCdiR6nj6G8BiGCwsf16wseYCyE54o7t7UA +spl-token display HWxYLBFfPxzrtGTKZL3VEN9dTDBz7qDgFXJVcw8zFkfq +*/ diff --git a/evm_loader/solidity/erc20_for_spl.sol b/solidity/erc20_for_spl.sol similarity index 100% rename from evm_loader/solidity/erc20_for_spl.sol rename to solidity/erc20_for_spl.sol diff --git a/evm_loader/solidity/erc20_for_spl_factory.sol b/solidity/erc20_for_spl_factory.sol similarity index 100% rename from evm_loader/solidity/erc20_for_spl_factory.sol rename to solidity/erc20_for_spl_factory.sol diff --git a/evm_loader/solidity/erc721_for_metaplex.sol b/solidity/erc721_for_metaplex.sol similarity index 100% rename from evm_loader/solidity/erc721_for_metaplex.sol rename to solidity/erc721_for_metaplex.sol diff --git a/evm_loader/solidity/lib_query_account.sol b/solidity/lib_query_account.sol similarity index 100% rename from evm_loader/solidity/lib_query_account.sol rename to solidity/lib_query_account.sol diff --git a/evm_loader/solidity/neon_wrapper.sol b/solidity/neon_wrapper.sol similarity index 100% rename from evm_loader/solidity/neon_wrapper.sol rename to solidity/neon_wrapper.sol