Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for building child contracts from source #50

Merged
merged 8 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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