From 97a7406af2ef5122ed5237b37fef08a5433b9690 Mon Sep 17 00:00:00 2001 From: ipopescu Date: Mon, 23 Oct 2023 13:48:47 +0200 Subject: [PATCH] Main content for writing factory contracts --- .../writing-factory-contracts.md | 155 +++++++++++++++++- 1 file changed, 151 insertions(+), 4 deletions(-) diff --git a/source/docs/casper/developers/writing-onchain-code/writing-factory-contracts.md b/source/docs/casper/developers/writing-onchain-code/writing-factory-contracts.md index bfbbb3615e..ca0fbb7924 100644 --- a/source/docs/casper/developers/writing-onchain-code/writing-factory-contracts.md +++ b/source/docs/casper/developers/writing-onchain-code/writing-factory-contracts.md @@ -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). -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. ::: + + +## 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. + + + +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. + +
+Sample installer code for a counter factory + +```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()); +} +``` + +
+ +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. + + + +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).