Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Interacting with contracts in tests #18

Merged
merged 3 commits into from
Jul 28, 2022
Merged
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
86 changes: 55 additions & 31 deletions docs/learn/interacting-with-contracts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,22 @@ to do in any other software development context: the contract transfers control
and data to another part of the _same_ contract. Because a function call always
transfers control to the same contract, they do not change the values returned
by `get_current_contract` and `get_invoking_contract`. A function call is the
only way to access the private methods of a contract.
only way to access the private methods of a contract, although it can also call
be used to access the public methods of a contract.

To perform a function call, simply make a Rust function call.

### Contract invokation
### Contract invocation

A contract invokation is a more powerful and more expensive kind of contract
interaction. A contract invokation is similar to starting a new process because
A contract invocation is a more powerful and more expensive kind of contract
interaction. A contract invocation is similar to starting a new process because
the code that runs will be in a separate address space, meaning that they do
not share any data other than what was passed in the invokation. While a
contract invokation typically transfers control to a _different_ contract, it
not share any data other than what was passed in the invocation. While a
contract invocation typically transfers control to a _different_ contract, it
is possible to transfer control to the currently running contract. Regardless
of whether the contract that receives control is a different contract or the
currently running contract, the value returned by `get_invoking_contract` will
be the previous value of `get_current_contract`. A contract invokation can only
be the previous value of `get_current_contract`. A contract invocation can only
access the public methods of a contract.

If a contract contains a public function `f`, then invoking `f` can be done by
Expand All @@ -39,36 +40,59 @@ An operation is the ultimate entrypoint of every contract interaction. An
operation transfers control and external data to a contract, allowing execution
to begin.

## Testing contracts with our testutils machinery
## Interacting with contracts in tests

[Debugging contracts](debugging-contracts.mdx) explains that it is much more
convenient to debug using native code than WASM. Given that you are testing
native code, it is tempting to interact with your contract directly using
function calls. If you attempt this approach, you will find that it doesn't
always work. Function call interactions do not set the environment into the
correct state for contract execution, so functions involving contract data and
determining the current or invoking contract will not work.

When writing tests, it is important to always interact with contracts through
contract invocation. In a production setting, contract invocation will execute
WASM bytecode loaded from the ledger. So how does this work if you are testing
native code? You must register your contract with the environment so it knows
what functions are available and how to call them. While this sounds complex,
the `contractimpl` procedural macro automatically generates almost all of the
code to do this. All you have to do is write a small stub to actually call the
generated code. An example of this can be seen in the [token contract](https://github.com/stellar/soroban-token-contract/blob/42380647bb817bf01c739c19286f18be881e0e41/src/testutils.rs#L12-L15)

We provide some machinery in the Soroban
[SDK](https://github.com/stellar/rs-soroban-sdk) that allows a user to
test contract calls by writing rust test cases.

### Example
```rust
pub fn register_test_contract(e: &Env, contract_id: &[u8; 32]) {
let contract_id = FixedBinary::from_array(e, *contract_id);
e.register_contract(&contract_id, crate::contract::Token {});
}
```

Let's look at the [single offer contract](https://github.com/stellar/soroban-examples/blob/main/single_offer/src/lib.rs) as an example -
Some contracts, such as the token contract, only export the contract invocation
functions. In doing so, they are able to assign those functions friendly names.
For example,
[initialize](https://github.com/stellar/soroban-token-contract/blob/42380647bb817bf01c739c19286f18be881e0e41/src/contract.rs#L55-L57)

```rust
#[contractimpl(export_if = "export")]
impl SingleOfferTrait for SingleOffer {
fn initialize(e: Env, admin: Identifier, sell_token: U256, buy_token: U256, n: u32, d: u32) {
...
impl TokenTrait for Token {
fn initialize(e: Env, admin: Identifier, decimal: u32, name: Binary, symbol: Binary) {
```

Notice the `contractimpl` procedural macro attribute. This attribute, along with
the `testutils` feature enabled, will allow the user to call a function in the
contract directly in a rust test case. For example, the single offer contract
has an alias `pub use crate::__initialize::call_internal as initialize` for the
`initialize` contract function, which is used
[here](https://github.com/stellar/soroban-examples/blob/56fef787395b5aed7cd7b19772cca28e21b3feb5/single_offer/src/testutils.rs#L45).
The rest of the `call_internal` aliases for the single offer contract can be
seen
[here](https://github.com/stellar/soroban-examples/blob/56fef787395b5aed7cd7b19772cca28e21b3feb5/single_offer/src/testutils.rs#L15)
in a `SingleOffer` wrapper class, and you can see how they're used in a [test
case](https://github.com/stellar/soroban-examples/blob/56fef787395b5aed7cd7b19772cca28e21b3feb5/single_offer/src/test.rs#L42).
is [exported](https://github.com/stellar/soroban-token-contract/blob/42380647bb817bf01c739c19286f18be881e0e41/src/lib.rs#L26)
as

```rust
pub use crate::contract::initialize::invoke as initialize;
```

Some contracts, such as the token contract, also provide a [friendlier interface](https://github.com/stellar/soroban-token-contract/blob/42380647bb817bf01c739c19286f18be881e0e41/src/testutils.rs#L26-L191)
to facilitate testing. There are many ways these interfaces might make testing
easier, but one common one is to allow automatic message signing by passing a
[ed25519_dalek::Keypair](https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.Keypair.html).

Note that everything described in this section is only available if the
`testutils` feature is enabled.

### Example

This machinery can also be used to test multiple contracts together. For
example, the single offer contract test case creates a token using the
[token
contract](https://github.com/stellar/soroban-examples/blob/56fef787395b5aed7cd7b19772cca28e21b3feb5/single_offer/src/test.rs#L22).
example, the single offer contract test case [creates a token](https://github.com/stellar/soroban-examples/blob/56fef787395b5aed7cd7b19772cca28e21b3feb5/single_offer/src/test.rs#L22).