Skip to content

Commit 32a7a45

Browse files
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 <[email protected]>
1 parent 5208294 commit 32a7a45

File tree

12 files changed

+96
-8
lines changed

12 files changed

+96
-8
lines changed

.github/workflows/test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ jobs:
5757
uses: actions/checkout@v3
5858
with:
5959
fetch-depth: 0
60+
submodules: recursive
6061
- name: 'Set up Docker'
6162
uses: ./.github/actions/with-docker
6263
with:

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "deps/soroban-examples"]
2+
path = deps/soroban-examples
3+
url = https://github.com/stellar/soroban-examples

deps/soroban-examples

Submodule soroban-examples added at 4ec1538

package/version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.44
1+
0.1.45

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "komet"
7-
version = "0.1.44"
7+
version = "0.1.45"
88
description = "K tooling for the Soroban platform"
99
authors = [
1010
"Runtime Verification, Inc. <[email protected]>",

src/komet/kasmer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def build_soroban_contract(self, contract_path: Path, out_dir: Path | None = Non
119119
Returns:
120120
The path to the compiled wasm contract.
121121
"""
122-
contract_stem = self.contract_manifest(contract_path)['name']
122+
contract_stem = self.contract_manifest(contract_path)['name'].replace('-', '_')
123123
contract_name = f'{contract_stem}.wasm'
124124
if out_dir is None:
125125
out_dir = Path(mkdtemp(f'komet_{str(contract_path.stem)}'))

src/komet/komet.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ def _exec_test(*, wasm: Path | None) -> None:
9393
# We build the contract here, specifying where it's saved so we know where to find it.
9494
# Knowing where the compiled contract is saved by default when building it would eliminate
9595
# the need for this step, but at the moment I don't know how to retrieve that information.
96+
child_wasms = _read_config_file(kasmer)
9697
wasm = kasmer.build_soroban_contract(Path.cwd())
97-
child_wasms = _read_config_file()
9898

9999
kasmer.deploy_and_run(wasm, child_wasms)
100100

@@ -109,22 +109,30 @@ def _exec_prove_run(
109109
child_wasms: tuple[Path, ...] = ()
110110

111111
if wasm is None:
112+
child_wasms = _read_config_file(kasmer)
112113
wasm = kasmer.build_soroban_contract(Path.cwd())
113-
child_wasms = _read_config_file()
114114

115115
kasmer.deploy_and_prove(wasm, child_wasms, id, proof_dir, bug_report)
116116

117117
sys.exit(0)
118118

119119

120-
def _read_config_file(dir_path: Path | None = None) -> tuple[Path, ...]:
120+
def _read_config_file(kasmer: Kasmer, dir_path: Path | None = None) -> tuple[Path, ...]:
121121
dir_path = Path.cwd() if dir_path is None else dir_path
122122
config_path = dir_path / 'kasmer.json'
123123

124+
def get_wasm_path(c: Path) -> Path:
125+
c = abs_or_rel_to(c, dir_path)
126+
if c.is_file() and c.suffix == '.wasm':
127+
return c
128+
if c.is_dir():
129+
return kasmer.build_soroban_contract(c)
130+
raise ValueError(f'Invalid child contract path: {c}')
131+
124132
if config_path.is_file():
125133
with open(config_path) as f:
126134
config = json.load(f)
127-
return tuple(abs_or_rel_to(Path(c), dir_path) for c in config['contracts'])
135+
return tuple(get_wasm_path(Path(c)) for c in config['contracts'])
128136

129137
return ()
130138

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "test_cross_contract"
3+
version = "0.0.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[lib]
8+
crate-type = ["cdylib"]
9+
doctest = false
10+
11+
[dependencies]
12+
soroban-sdk = { workspace = true }
13+
14+
[dev-dependencies]
15+
soroban-sdk = { workspace = true, features = ["testutils"] }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
This test demonstrates deploying two contracts (`contract_a and contract_b`) and testing their interaction.
2+
`contract_b` calls a function in `contract_a` during execution. The test setup is defined in `kasmer.json`,
3+
listing the directories of the two contracts.
4+
Komet automatically compiles the contracts and runs the test.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"contracts": [
3+
"../../../../../../../deps/soroban-examples/cross_contract/contract_a",
4+
"../../../../../../../deps/soroban-examples/cross_contract/contract_b"
5+
]
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#![no_std]
2+
use soroban_sdk::{contract, contractclient, contractimpl, symbol_short, Address, Bytes, Env, FromVal, Symbol, Val};
3+
4+
extern "C" {
5+
fn kasmer_create_contract(addr_val: u64, hash_val: u64) -> u64;
6+
}
7+
8+
fn create_contract(env: &Env, addr: &Bytes, hash: &Bytes) -> Address {
9+
unsafe {
10+
let res = kasmer_create_contract(addr.as_val().get_payload(), hash.as_val().get_payload());
11+
Address::from_val(env, &Val::from_payload(res))
12+
}
13+
}
14+
15+
#[contract]
16+
pub struct TestCrossContract;
17+
18+
#[contractclient(name = "ContractBClient")]
19+
trait ContractB {
20+
fn add_with(e: Env, address: Address, x: u32, y: u32) -> u32;
21+
}
22+
23+
const ADDR_A: &[u8; 32] = b"contract_a______________________";
24+
const ADDR_A_KEY: Symbol = symbol_short!("ctr_a");
25+
const ADDR_B: &[u8; 32] = b"contract_b______________________";
26+
const ADDR_B_KEY: Symbol = symbol_short!("ctr_b");
27+
28+
#[contractimpl]
29+
impl TestCrossContract {
30+
pub fn init(env: Env, hash_a: Bytes, hash_b: Bytes) {
31+
let address_a = create_contract(&env, &Bytes::from_array(&env, ADDR_A), &hash_a);
32+
let address_b = create_contract(&env, &Bytes::from_array(&env, ADDR_B), &hash_b);
33+
34+
env.storage().instance().set(&ADDR_A_KEY, &address_a);
35+
env.storage().instance().set(&ADDR_B_KEY, &address_b);
36+
}
37+
38+
pub fn test_add_with(env: Env, x: u32, y: u32) -> bool {
39+
if x > 100 || y > 100 {
40+
return true;
41+
}
42+
43+
let address_a : Address = env.storage().instance().get(&ADDR_A_KEY).unwrap();
44+
let address_b : Address = env.storage().instance().get(&ADDR_B_KEY).unwrap();
45+
46+
let client = ContractBClient::new(&env, &address_b);
47+
x + y == client.add_with(&address_a, &x, &y)
48+
}
49+
50+
}

src/tests/integration/test_integration.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_run(program: Path, tmp_path: Path) -> None:
3636
def test_komet(contract_path: Path, tmp_path: Path, concrete_kasmer: Kasmer) -> None:
3737
# Given
3838
contract_wasm = concrete_kasmer.build_soroban_contract(contract_path, tmp_path)
39-
child_wasms = _read_config_file(contract_path)
39+
child_wasms = _read_config_file(concrete_kasmer, contract_path)
4040

4141
# Then
4242
if contract_path.stem.endswith('_fail'):

0 commit comments

Comments
 (0)