Skip to content

Commit

Permalink
Main content for writing factory contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
ipopescu committed Oct 23, 2023
1 parent c9a2235 commit 97a7406
Showing 1 changed file with 151 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,163 @@ title: Factory Contracts

# Writing Factory Contracts

This guide presents a contract factory for simple counter contracts. The goal is to showcase the factory pattern and the Casper APIs that support this pattern. The example contract factory is a modified counter contract found [here](https://github.com/mpapierski/casper-node/blob/gh-2064-factory-pattern/smart_contracts/contracts/test/counter-factory/src/main.rs).
This guide presents a contract factory for simple counter contracts. The goal is to showcase the [factory pattern](https://github.com/casper-network/ceps/pull/86/files) and the Casper APIs that support this pattern. The example contract factory used in this guide is a modified counter contract found [here](https://github.com/mpapierski/casper-node/blob/gh-2064-factory-pattern/smart_contracts/contracts/test/counter-factory/src/main.rs).
<!-- TODO before publishing the docs: point to the new link once the casper-node repository is updated. Or, move this counter factory example to https://github.com/casper-ecosystem/tutorials-example-wasm.-->

The factory pattern is a widely recognized software design concept used in various programming contexts. DApp developers often use the factory pattern to create smart contracts from a given factory contract. The factory pattern ensures that the contracts produced maintain a specified behavior according to a template by providing specific entry points and accepting certain arguments at runtime. Simply put, the factory contract produces other smart contracts.
The factory pattern is a widely recognized software design concept used in various programming contexts. DApp developers often use the factory pattern to create smart contracts from a given factory contract. The factory pattern ensures that the contracts produced maintain a specified behavior, such as specific entry points and arguments. Thus, factories produce other smart contracts according to a template.

Factory contracts are created using the entry point type called `EntryPointType::Install`, which marks an entry point as a factory method responsible for creating and installing contracts on the chain. The `EntryPointAccess::Template` is also needed to mark an entry point as existing in the bytecode but not callable, thus referencing Wasm exports from within entry points marked with `EntryPointType::Install`. The [CEP-86](https://github.com/casper-network/ceps/pull/86/files) proposal provides additional background and details.
Casper factory contracts are created using the entry point type called `EntryPointType::Install`, which marks an entry point as a factory method responsible for creating and installing contracts on the chain. This installer entry point will derive the Wasm installed on the chain and create a new contract with the same Wasm, just different sets of entry points as required. In other words, these installer entry points marked with `EntryPointType::Install` are the contract factories.

The `EntryPointAccess::Template` marks an entry point as existing in the bytecode but not callable. Thus, regular entry points can be referenced from within installer entry points marked with `EntryPointType::Install`. In object-oriented terms, entry points marked with `EntryPointAccess::Template` act as virtual abstract methods and cannot be called from session code. The Wasm for template entry points is declared at the factory level in the installer logic.

:::note

Factory contracts pose a known drawback. All the smart contracts created with the factory pattern share the same bytecode installed on the chain. Thus, developers cannot modify the Wasm and thus create new, modified contracts with the factory contract. Also, developers must specify all the possible entry points in the factory contract and tag them with the `EntryPointAccess::Template` marker.
Factory contracts pose a known drawback. All the smart contracts created with the factory pattern share the same Wasm installed on the chain. Thus, developers cannot modify the Wasm once installed and create modified contracts with the factory contract. Developers must specify all the possible entry points in the factory contract and tag them with the `EntryPointAccess::Template` marker.

:::

<!-- TODO Diagram#1 create a diagram with the dev team and insert it here. -->

## The Counter Factory Example

This section dives into a [simple counter factory](https://github.com/mpapierski/casper-node/blob/gh-2064-factory-pattern/smart_contracts/contracts/test/counter-factory/src/main.rs) to describe how to implement the factory pattern on a Casper network. The [Counter on the Testnet Tutorial](https://docs.casper.network/resources/beginner/counter-testnet/walkthrough/) demonstrates the non-factory version of the counter contract.

<!-- TODO before publishing the docs: point to the new link once the casper-node repository is updated.
Or, move this counter factory example to https://github.com/casper-ecosystem/tutorials-example-wasm. If using this, add a step to "clone the repository". -->

Let's start by exploring the [session code](https://github.com/mpapierski/casper-node/blob/a4d7d5a4f67e7860b2e8c57d74c864860b4e74c8/smart_contracts/contracts/test/counter-factory/src/main.rs#L115), where the contract entry points are defined.

Two installer entry points are marked with `EntryPointType::Install`, meaning these entry points will produce new counter contracts once this Wasm is installed in global state. They are also marked with `EntryPointAccess::Public`, so they can be called from the session code.

```rust
let entry_point: EntryPoint = EntryPoint::new(
CONTRACT_FACTORY_ENTRY_POINT.to_string(),
Parameters::new(),
CLType::Unit,
EntryPointAccess::Public,
EntryPointType::Install,
);
entry_points.add_entry_point(entry_point);
let entry_point: EntryPoint = EntryPoint::new(
CONTRACT_FACTORY_DEFAULT_ENTRY_POINT.to_string(),
Parameters::new(),
CLType::Unit,
EntryPointAccess::Public,
EntryPointType::Install,
);
```

These two installers show how to declare multiple factory entry points and use them to initialize the Wasm they produce with different values. On [line 61](https://github.com/mpapierski/casper-node/blob/a4d7d5a4f67e7860b2e8c57d74c864860b4e74c8/smart_contracts/contracts/test/counter-factory/src/main.rs#L61C19-L61C35), the `contract_factory` entry point creates a counter contract with a given name and initial value.

```rust
#[no_mangle]
pub extern "C" fn contract_factory() {
let name: String = runtime::get_named_arg(ARG_NAME);
let initial_value: U512 = runtime::get_named_arg(ARG_INITIAL_VALUE);
installer(name, initial_value);
}
```

On [line 68](https://github.com/mpapierski/casper-node/blob/a4d7d5a4f67e7860b2e8c57d74c864860b4e74c8/smart_contracts/contracts/test/counter-factory/src/main.rs#L68), the `contract_factory_default` entry point creates a counter contract with a given name and a zero initial value.

```rust
#[no_mangle]
pub extern "C" fn contract_factory_default() {
let name: String = runtime::get_named_arg(ARG_NAME);
installer(name, U512::zero());
}
```

The [installer function](https://github.com/mpapierski/casper-node/blob/a4d7d5a4f67e7860b2e8c57d74c864860b4e74c8/smart_contracts/contracts/test/counter-factory/src/main.rs#L73) creates a new counter contract, by specifying its named keys and entry points. The named keys include the counter's initial value, and the entry points define the counter's `decrement` and `increment` functionality. These entry points are defined just like in any other smart contract, with `EntryPointAccess::Public` and `EntryPointType::Contract`, and they are callable for all the counters created. To learn how to call the `increment` and `decrement` functions, see the [Counter on the Testnet Tutorial](../../resources/beginner/counter-testnet/walkthrough/), which is the non-factory version of the counter contract.

<details>
<summary>Sample installer code for a counter factory</summary>

```rust
fn installer(name: String, initial_value: U512) {
let named_keys = {
let new_uref = storage::new_uref(initial_value);
let mut named_keys = NamedKeys::new();
named_keys.insert(CURRENT_VALUE_KEY.to_string(), new_uref.into());
named_keys
};

let entry_points = {
let mut entry_points = EntryPoints::new();
let entry_point: EntryPoint = EntryPoint::new(
INCREASE_ENTRY_POINT.to_string(),
Parameters::new(),
CLType::Unit,
EntryPointAccess::Public,
EntryPointType::Contract,
);
entry_points.add_entry_point(entry_point);
let entry_point: EntryPoint = EntryPoint::new(
DECREASE_ENTRY_POINT.to_string(),
Parameters::new(),
CLType::Unit,
EntryPointAccess::Public,
EntryPointType::Contract,
);
entry_points.add_entry_point(entry_point);

entry_points
};

let (contract_hash, contract_version) = storage::new_contract(
entry_points,
Some(named_keys),
Some(PACKAGE_HASH_KEY_NAME.to_string()),
Some(ACCESS_KEY_NAME.to_string()),
);

runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into());
runtime::put_key(&name, contract_hash.into());
}
```

</details>

It is important to note that the installer logic [saves the newly created contract version and contract hash](https://github.com/mpapierski/casper-node/blob/a4d7d5a4f67e7860b2e8c57d74c864860b4e74c8/smart_contracts/contracts/test/counter-factory/src/main.rs#L110-L111) under the factory contract's named keys. The installer logic runs within the factory contract context, not as part of the session code running within the account context. For more details, see the [comparison between session and contract context](../writing-onchain-code/contract-vs-session/#comparing-session-and-contract).

```rust
runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into());
runtime::put_key(&name, contract_hash.into());
```

For example, if you install the factory counter contract, you will see only one named key for this contract in your account, with the two installer entry points `contract_factory` and `contract_factory_default`. See [lines 155-163](https://github.com/mpapierski/casper-node/blob/a4d7d5a4f67e7860b2e8c57d74c864860b4e74c8/smart_contracts/contracts/test/counter-factory/src/main.rs#L155C1-L163).

If you call the installer three times to create 3 different counters, you will see 3 named keys for each counter in the factory contract's named keys. The counter contracts produced will have the `increment` and `decrement` entry points.

<!-- TODO Diagram#2 create a diagram with the dev team and insert it here. Or take screenshots from cspr.live depicting this setup. -->

As explained above, developers must define all the possible non-installer entry points in the factory contract and tag them with the `EntryPointAccess::Template` and `EntryPointType::Contract` markers. See [lines 135-139](https://github.com/mpapierski/casper-node/blob/a4d7d5a4f67e7860b2e8c57d74c864860b4e74c8/smart_contracts/contracts/test/counter-factory/src/main.rs#L135C9-L149C11):

```rust
let entry_point: EntryPoint = EntryPoint::new(
INCREASE_ENTRY_POINT.to_string(),
Parameters::new(),
CLType::Unit,
EntryPointAccess::Template,
EntryPointType::Contract,
);
entry_points.add_entry_point(entry_point);
let entry_point: EntryPoint = EntryPoint::new(
DECREASE_ENTRY_POINT.to_string(),
Parameters::new(),
CLType::Unit,
EntryPointAccess::Template,
EntryPointType::Contract,
);
```

:::warning

Suppose developers forget to declare an entry point in the outermost session logic (the `call` function) and mark it with `EntryPointAccess::Template`; that Wasm export will be stripped when the factory contract is installed in global state. Creating the entry point in the installer logic is not sufficient.

:::


## What's Next? {#whats-next}

- Learn to [test a factory contract](./testing-factory-contracts.md).

0 comments on commit 97a7406

Please sign in to comment.