Skip to content
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
132 changes: 85 additions & 47 deletions docs/basics/cross-contract-calling.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,50 +14,64 @@ on-chain contracts.
There are a few approaches to performing these cross-contract calls in ink!:
1. Contract references (i.e `ContractRef`)
2. Builders (i.e `CreateBuilder` and `CallBuilder`)
3. Calling Solidity ABI Encoded Contracts with `CallBuilder`

Contract references can only be used for cross-contract calls to other ink! contracts.
Builders can be used to issue cross-contract calls to any RISC-V contract, such as those
written in ink! or Solidity.
:::note
In general, contract references should be preferred over builders
because they provide higher-level type-safe interfaces.
Only use builders if you need to manipulate low-level call parameters.
:::

## Contract References

Contract references refer to structs that are generated by the ink! code generation for the
purposes of cross-contract calls.
Contract references are wrapper types that can be used for interacting with an on-chain/"callee" contract
using a high-level type-safe interface.

They give developers a type-safe way of interacting with a contract.
They are either statically generated by the ink! code generation (for contract dependencies),
or they can be manually defined as dynamic interfaces using the [`#[ink::contract_ref]` attribute][contract-ref-attr].

A downside to using them is that you need to import the contract you want to call as a
dependency of your own contract.
[contract-ref-attr]: ../macros-attributes/contract_ref.md

If you want to interact with a contract that is already on-chain you will need to use the
[`Builders`](#builders) approach instead.
### Manually defined contract references

:::note
In ["all" ABI mode][abi-all], contract references are generated for both ink!/native
and Solidity ABI calling conventions, with the Solidity ABI specific contract reference
named with an additional `Sol` suffix
(e.g. `ContractRefSol` for a contract named `Contract`).
See our section on using the [`#[ink::contract_ref]` attribute][contract-ref-attr]
for a detailed description and examples of how to manually define the dynamic interface
for an on-chain/"callee" contract, and use the generated contract reference
for calling the on-chain/"callee" contract in a type-safe manner.

Note that, this `Sol` suffix is not necessary in ["sol" ABI mode][abi-sol]
(i.e. for a contract named `Contract`, `ContractRef` will use the Solidity ABI
calling conventions in "sol" ABI mode).
:::caution
A downside to manually defined contract references is that mistakes
in the interface definition are not caught at compile-time.

It's therefore important to make sure such interfaces are properly tested
using [end-to-end testing][e2e-test] before contracts are deployed on-chain.
:::

[abi-all]: ./abi/all.md
[abi-sol]: ./abi/solidity.md
[e2e-test]: ../testing/e2e.md

### Statically generated contract references

### `BasicContractRef` walkthrough
To use statically generated contract references, you need to import the contract
you want to call as a dependency of your own contract.

We will walk through the [`cross-contract-calls`](https://github.com/use-ink/ink-examples/tree/master/cross-contract-calls)
example in order to demonstrate how cross-contract calls using contract references work.
This means that this approach cannot be used if you want to interact with a contract
that is either built in another language (e.g. Solidity), or has no publicly available package/crate.
For those cases, you will need to use either [manually defined contract references](#manually-defined-contract-references)
using the [`#[ink::contract_ref]` attribute][contract-ref-attr] (recommended),
or the [`Builders`](#builders) approach instead.

#### `BasicContractRef` walkthrough

We will walk through the [`cross-contract-calls`][example] example in order
to demonstrate how cross-contract calls using contract references work.

The general workflow will be:
1. Prepare `OtherContract` to be imported to other contracts
1. Import `OtherContract` into `BasicContractRef`
1. Upload `OtherContract` on-chain
1. Instantiate `OtherContract` using `BasicContractRef`
1. Call `OtherContract` using `BasicContractRef`
2. Import `OtherContract` into `BasicContractRef`
3. Upload `OtherContract` on-chain
4. Instantiate `OtherContract` using `BasicContractRef`
5. Call `OtherContract` using `BasicContractRef`

[example]: https://github.com/use-ink/ink-examples/tree/master/cross-contract-calls

#### Prepping `OtherContract`

Expand All @@ -70,6 +84,24 @@ We do this by re-exporting the contract reference as follows:
pub use self::other_contract::OtherContractRef;
```

:::info
We intend to automatically generate this re-export in future releases of ink! v6.
:::

:::note
In ["all" ABI mode][abi-all], contract references are generated for both ink!/native
and Solidity ABI calling conventions, with the Solidity ABI specific contract reference
named with an additional `Sol` suffix
(e.g. `ContractRefSol` for a contract named `Contract`).

Note that, this `Sol` suffix is not necessary in ["sol" ABI mode][abi-sol]
(i.e. for a contract named `Contract`, `ContractRef` will use the Solidity ABI
calling conventions in "sol" ABI mode).
:::

[abi-all]: ./abi/all.md
[abi-sol]: ./abi/solidity.md

#### Importing `OtherContract`

Next, we need to import `OtherContract` to our `BasicContractRef` contract.
Expand Down Expand Up @@ -206,23 +238,30 @@ Data Ok(true)
```

## Builders
The
[`CreateBuilder`](https://use-ink.github.io/ink/ink_env/call/struct.CreateBuilder.html)
and
[`CallBuilder`](https://use-ink.github.io/ink/ink_env/call/struct.CallBuilder.html)
offer low-level, flexible interfaces for performing cross-contract calls. The
`CreateBuilder` allows you to instantiate already uploaded contracts, and the
`CallBuilder` allows you to call messages on instantiated contracts.
The [`CreateBuilder`][create-builder] and [`CallBuilder`][call-builder]
offer low-level, flexible interfaces for performing cross-contract calls.
The `CreateBuilder` allows you to instantiate already uploaded contracts,
and the `CallBuilder` allows you to call messages on instantiated contracts.

[create-builder]: https://use-ink.github.io/ink/ink_env/call/struct.CreateBuilder.html
[call-builder]: https://use-ink.github.io/ink/ink_env/call/struct.CallBuilder.html

:::caution
A downside to low-level `CreateBuilder`s and `CallBuilder`s is that mistakes
in the generated calls (e.g. wrong selectors, wrong order and/or types of arguments e.t.c)
are not caught at compile-time.

It's therefore important to make sure such calls are properly tested
using [end-to-end testing][e2e-test] before contracts are deployed on-chain.
:::

### CreateBuilder
The `CreateBuilder` offers an an easy way for you to **instantiate** a contract. Note
that you'll still need this contract to have been previously uploaded.
The `CreateBuilder` offers an easy way for you to **instantiate** a contract.
Note that you'll still need this contract to have been previously uploaded.

:::note

For a refresher on the difference between `upload` and `instantiate`
[see here](../getting-started/deploying.md).

:::

In order to instantiate a contract you need a reference to your contract, just like in
Expand All @@ -243,9 +282,8 @@ Below is an example of how to instantiate a contract using the `CreateBuilder`.
```rust
use contract::MyContractRef;
let my_contract: MyContractRef = build_create::<MyContractRef>()
.instantiate_v1()
.code_hash(Hash::from([0x42; 32]))
.gas_limit(0)
.ref_time_limit(0)
.endowment(10)
.exec_input(
ExecutionInput::new(Selector::new(ink::selector_bytes!("new")))
Expand Down Expand Up @@ -299,8 +337,7 @@ Below is an example of how to call a contract using the `CallBuilder`. We will:
```rust
let my_return_value = build_call::<DefaultEnvironment>()
.call(H160::from([0x42; 20]))
.call_v1()
.gas_limit(0)
.ref_time_limit(0)
.transferred_value(10)
.exec_input(
ExecutionInput::new(Selector::new(ink::selector_bytes!("flip")))
Expand All @@ -312,14 +349,15 @@ let my_return_value = build_call::<DefaultEnvironment>()
.invoke();
```

Note:

:::caution
Message arguments will be encoded in the order in which they are provided to the `CallBuilder`.
This means that they should match the order (and type) they appear in the function
signature.

You will not be able to get any feedback about this at compile time. You will only
find out your call failed at runtime!
You will not get any feedback about this at compile-time,
so it's important to make sure such calls are properly tested
with [end-to-end testing][e2e-test] before contracts are deployed on-chain.
:::

:::note
To call messages from a contract that uses a different [ABI][abi] than your contract,
Expand Down
11 changes: 11 additions & 0 deletions docs/macros-attributes/contract_ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ mod erc20_caller {
}
```

:::caution
A downside to manually defined contract references is that mistakes
in the interface definition are not caught at compile-time.

It's therefore important to make sure such interfaces are properly tested
using [end-to-end testing][e2e-test] before contracts are deployed on-chain.
:::

[e2e-test]: ../testing/e2e.md

## Header Arguments

The `#[ink::contract_ref]` macro can be provided with some additional
Expand Down Expand Up @@ -125,6 +135,7 @@ pub struct MyEnvironment;

impl ink_env::Environment for MyEnvironment {
const NATIVE_TO_ETH_RATIO: u32 = 100_000_000;

type AccountId = [u8; 16];
type Balance = u128;
type Hash = [u8; 32];
Expand Down