From 32a7a4546645b455424ded5ac85e2f40e0a9fb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Bilge=20Yal=C3=A7=C4=B1nkaya?= Date: Wed, 4 Dec 2024 10:42:00 +0300 Subject: [PATCH] Add support for building child contracts from source (#50) * add submodule soroban-examples * fix contract stem * allow contract paths in kasmer.json and build from source * add test_cross_contract * Set Version: 0.1.43 * github action: checkout submodules * Set Version: 0.1.45 --------- Co-authored-by: devops --- .github/workflows/test.yml | 1 + .gitmodules | 3 ++ deps/soroban-examples | 1 + package/version | 2 +- pyproject.toml | 2 +- src/komet/kasmer.py | 2 +- src/komet/komet.py | 16 ++++-- .../contracts/test_cross_contract/Cargo.toml | 15 ++++++ .../contracts/test_cross_contract/README.md | 4 ++ .../contracts/test_cross_contract/kasmer.json | 6 +++ .../contracts/test_cross_contract/src/lib.rs | 50 +++++++++++++++++++ src/tests/integration/test_integration.py | 2 +- 12 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 .gitmodules create mode 160000 deps/soroban-examples create mode 100644 src/tests/integration/data/soroban/contracts/test_cross_contract/Cargo.toml create mode 100644 src/tests/integration/data/soroban/contracts/test_cross_contract/README.md create mode 100644 src/tests/integration/data/soroban/contracts/test_cross_contract/kasmer.json create mode 100644 src/tests/integration/data/soroban/contracts/test_cross_contract/src/lib.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4687587a..6610a96e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,6 +57,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + submodules: recursive - name: 'Set up Docker' uses: ./.github/actions/with-docker with: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..d4e110f6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/soroban-examples"] + path = deps/soroban-examples + url = https://github.com/stellar/soroban-examples diff --git a/deps/soroban-examples b/deps/soroban-examples new file mode 160000 index 00000000..4ec1538f --- /dev/null +++ b/deps/soroban-examples @@ -0,0 +1 @@ +Subproject commit 4ec1538f9d9ea77caaab9c315568c37d0d2dce2f diff --git a/package/version b/package/version index 6871b9e7..6ee33ba2 100644 --- a/package/version +++ b/package/version @@ -1 +1 @@ -0.1.44 +0.1.45 diff --git a/pyproject.toml b/pyproject.toml index 401a3c1b..e82e6513 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "komet" -version = "0.1.44" +version = "0.1.45" description = "K tooling for the Soroban platform" authors = [ "Runtime Verification, Inc. ", diff --git a/src/komet/kasmer.py b/src/komet/kasmer.py index a33617b7..96934a1c 100644 --- a/src/komet/kasmer.py +++ b/src/komet/kasmer.py @@ -119,7 +119,7 @@ def build_soroban_contract(self, contract_path: Path, out_dir: Path | None = Non Returns: The path to the compiled wasm contract. """ - contract_stem = self.contract_manifest(contract_path)['name'] + contract_stem = self.contract_manifest(contract_path)['name'].replace('-', '_') contract_name = f'{contract_stem}.wasm' if out_dir is None: out_dir = Path(mkdtemp(f'komet_{str(contract_path.stem)}')) diff --git a/src/komet/komet.py b/src/komet/komet.py index b00d0dc6..f3a0fcc8 100644 --- a/src/komet/komet.py +++ b/src/komet/komet.py @@ -93,8 +93,8 @@ def _exec_test(*, wasm: Path | None) -> None: # We build the contract here, specifying where it's saved so we know where to find it. # Knowing where the compiled contract is saved by default when building it would eliminate # the need for this step, but at the moment I don't know how to retrieve that information. + child_wasms = _read_config_file(kasmer) wasm = kasmer.build_soroban_contract(Path.cwd()) - child_wasms = _read_config_file() kasmer.deploy_and_run(wasm, child_wasms) @@ -109,22 +109,30 @@ def _exec_prove_run( child_wasms: tuple[Path, ...] = () if wasm is None: + child_wasms = _read_config_file(kasmer) wasm = kasmer.build_soroban_contract(Path.cwd()) - child_wasms = _read_config_file() kasmer.deploy_and_prove(wasm, child_wasms, id, proof_dir, bug_report) sys.exit(0) -def _read_config_file(dir_path: Path | None = None) -> tuple[Path, ...]: +def _read_config_file(kasmer: Kasmer, dir_path: Path | None = None) -> tuple[Path, ...]: dir_path = Path.cwd() if dir_path is None else dir_path config_path = dir_path / 'kasmer.json' + def get_wasm_path(c: Path) -> Path: + c = abs_or_rel_to(c, dir_path) + if c.is_file() and c.suffix == '.wasm': + return c + if c.is_dir(): + return kasmer.build_soroban_contract(c) + raise ValueError(f'Invalid child contract path: {c}') + if config_path.is_file(): with open(config_path) as f: config = json.load(f) - return tuple(abs_or_rel_to(Path(c), dir_path) for c in config['contracts']) + return tuple(get_wasm_path(Path(c)) for c in config['contracts']) return () diff --git a/src/tests/integration/data/soroban/contracts/test_cross_contract/Cargo.toml b/src/tests/integration/data/soroban/contracts/test_cross_contract/Cargo.toml new file mode 100644 index 00000000..8e249162 --- /dev/null +++ b/src/tests/integration/data/soroban/contracts/test_cross_contract/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "test_cross_contract" +version = "0.0.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] +doctest = false + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/src/tests/integration/data/soroban/contracts/test_cross_contract/README.md b/src/tests/integration/data/soroban/contracts/test_cross_contract/README.md new file mode 100644 index 00000000..77fd68cf --- /dev/null +++ b/src/tests/integration/data/soroban/contracts/test_cross_contract/README.md @@ -0,0 +1,4 @@ +This test demonstrates deploying two contracts (`contract_a and contract_b`) and testing their interaction. +`contract_b` calls a function in `contract_a` during execution. The test setup is defined in `kasmer.json`, +listing the directories of the two contracts. +Komet automatically compiles the contracts and runs the test. \ No newline at end of file diff --git a/src/tests/integration/data/soroban/contracts/test_cross_contract/kasmer.json b/src/tests/integration/data/soroban/contracts/test_cross_contract/kasmer.json new file mode 100644 index 00000000..ae053884 --- /dev/null +++ b/src/tests/integration/data/soroban/contracts/test_cross_contract/kasmer.json @@ -0,0 +1,6 @@ +{ + "contracts": [ + "../../../../../../../deps/soroban-examples/cross_contract/contract_a", + "../../../../../../../deps/soroban-examples/cross_contract/contract_b" + ] +} \ No newline at end of file diff --git a/src/tests/integration/data/soroban/contracts/test_cross_contract/src/lib.rs b/src/tests/integration/data/soroban/contracts/test_cross_contract/src/lib.rs new file mode 100644 index 00000000..ea396b59 --- /dev/null +++ b/src/tests/integration/data/soroban/contracts/test_cross_contract/src/lib.rs @@ -0,0 +1,50 @@ +#![no_std] +use soroban_sdk::{contract, contractclient, contractimpl, symbol_short, Address, Bytes, Env, FromVal, Symbol, Val}; + +extern "C" { + fn kasmer_create_contract(addr_val: u64, hash_val: u64) -> u64; +} + +fn create_contract(env: &Env, addr: &Bytes, hash: &Bytes) -> Address { + unsafe { + let res = kasmer_create_contract(addr.as_val().get_payload(), hash.as_val().get_payload()); + Address::from_val(env, &Val::from_payload(res)) + } +} + +#[contract] +pub struct TestCrossContract; + +#[contractclient(name = "ContractBClient")] +trait ContractB { + fn add_with(e: Env, address: Address, x: u32, y: u32) -> u32; +} + +const ADDR_A: &[u8; 32] = b"contract_a______________________"; +const ADDR_A_KEY: Symbol = symbol_short!("ctr_a"); +const ADDR_B: &[u8; 32] = b"contract_b______________________"; +const ADDR_B_KEY: Symbol = symbol_short!("ctr_b"); + +#[contractimpl] +impl TestCrossContract { + pub fn init(env: Env, hash_a: Bytes, hash_b: Bytes) { + let address_a = create_contract(&env, &Bytes::from_array(&env, ADDR_A), &hash_a); + let address_b = create_contract(&env, &Bytes::from_array(&env, ADDR_B), &hash_b); + + env.storage().instance().set(&ADDR_A_KEY, &address_a); + env.storage().instance().set(&ADDR_B_KEY, &address_b); + } + + pub fn test_add_with(env: Env, x: u32, y: u32) -> bool { + if x > 100 || y > 100 { + return true; + } + + let address_a : Address = env.storage().instance().get(&ADDR_A_KEY).unwrap(); + let address_b : Address = env.storage().instance().get(&ADDR_B_KEY).unwrap(); + + let client = ContractBClient::new(&env, &address_b); + x + y == client.add_with(&address_a, &x, &y) + } + +} diff --git a/src/tests/integration/test_integration.py b/src/tests/integration/test_integration.py index e0ac2233..c518bcb0 100644 --- a/src/tests/integration/test_integration.py +++ b/src/tests/integration/test_integration.py @@ -36,7 +36,7 @@ def test_run(program: Path, tmp_path: Path) -> None: def test_komet(contract_path: Path, tmp_path: Path, concrete_kasmer: Kasmer) -> None: # Given contract_wasm = concrete_kasmer.build_soroban_contract(contract_path, tmp_path) - child_wasms = _read_config_file(contract_path) + child_wasms = _read_config_file(concrete_kasmer, contract_path) # Then if contract_path.stem.endswith('_fail'):