Skip to content

Commit

Permalink
Add support for building child contracts from source (#50)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
bbyalcinkaya and devops authored Dec 4, 2024
1 parent 5208294 commit 32a7a45
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 8 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "deps/soroban-examples"]
path = deps/soroban-examples
url = https://github.com/stellar/soroban-examples
1 change: 1 addition & 0 deletions deps/soroban-examples
Submodule soroban-examples added at 4ec153
2 changes: 1 addition & 1 deletion package/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.44
0.1.45
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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. <[email protected]>",
Expand Down
2 changes: 1 addition & 1 deletion src/komet/kasmer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}'))
Expand Down
16 changes: 12 additions & 4 deletions src/komet/komet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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 ()

Expand Down
Original file line number Diff line number Diff line change
@@ -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"] }
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"contracts": [
"../../../../../../../deps/soroban-examples/cross_contract/contract_a",
"../../../../../../../deps/soroban-examples/cross_contract/contract_b"
]
}
Original file line number Diff line number Diff line change
@@ -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)
}

}
2 changes: 1 addition & 1 deletion src/tests/integration/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'):
Expand Down

0 comments on commit 32a7a45

Please sign in to comment.