Skip to content

Commit c9c01fa

Browse files
committed
Address review comments.
1 parent 1aec302 commit c9c01fa

File tree

2 files changed

+44
-40
lines changed

2 files changed

+44
-40
lines changed

source/mainnet/smart-contracts/guides/factory-pattern.rst

+41-39
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ Using the factory pattern on Concordium
99
This guide makes use of features that are first available in protocol version 7,
1010
namely getting the contract name and module reference of a smart contract instance
1111
from a smart contract.
12-
Before the protocol upgrade to version 7, these features will not work.
12+
Before the protocol upgrade to version 7 (expected summer 2024), these features will not work.
1313

1414
The factory pattern is a design pattern where one contract (the factory) creates instances of
1515
another contract (the products). This pattern makes sense on Ethereum, where deploying a smart
1616
contract typically means deploying the code and instantiating a contract in a single transaction.
1717
This means that creating a new instance of a smart contract typically means redeploying the code,
1818
even if it is identical to an already-deployed smart contract. This can be undesirable:
19-
redeploying the same code repeatedly is a waste of resources. The factory pattern provides a
19+
redeploying the same code repeatedly wastes resources. The factory pattern provides a
2020
workaround for this: the contract code is deployed once, creating a factory contract, which then
2121
can be invoked to create further (product) contracts.
2222

@@ -35,7 +35,7 @@ redeploying the code. This eliminates one of the key motivations for using a fac
3535
to create?
3636

3737
This is where the factory pattern comes in. In the case of the factory class pattern,
38-
the code is parametrised by a `DatabaseConnectionFactory` object, which provides a
38+
the code is parametrized by a `DatabaseConnectionFactory` object, which provides a
3939
method for constructing `DatabaseConnection` objects. The `DatabaseConnectionFactory`
4040
interface would be implemented by `MySqlDatabaseConnectionFactory` and
4141
`SQLiteDatabaseConnectionFactory` (and potentially others). Which specific database connection
@@ -48,7 +48,7 @@ redeploying the code. This eliminates one of the key motivations for using a fac
4848
The point of this is that in object-oriented languages, the factory pattern is solving a
4949
particular problem: creating instances of a type that is not fully determined. While it may
5050
be idiomatic in one language, it may not be in another. For instance, in Rust the same code
51-
could be parametrised by a generic type that implements a `DatabaseConnection` trait, which
51+
could be parametrized by a generic type that implements a `DatabaseConnection` trait, which
5252
provides a `new` function for constructing `DatabaseConnection` instances. The particular
5353
type of database connection that is created would depend on how the generic type parameter
5454
is eventually instantiated.
@@ -63,24 +63,24 @@ relationship with the products that it produces. In particular, the factory coul
6363
index of product instances, or ensure that each instance is created distinctly from the others.
6464
It could also play a role in logging events signalling the creation of new products.
6565

66-
The sequence diagram below illustrates an idealised factory pattern.
66+
The sequence diagram below illustrates an idealized factory pattern.
6767
The user invokes the ``produce`` endpoint on a ``Factory`` contract.
6868
The factory constructs a new instance of the ``product`` smart contract, invoking the
6969
``init_product`` constructor.
7070
Finally, a reference to the new product instance is returned.
7171

7272
.. image:: images/ideal-factory.svg
73-
:alt: sequence diagram showing the idealised interaction for the factory pattern
73+
:alt: sequence diagram showing the idealized interaction for the factory pattern
7474

7575

7676
Unfortunately, implementing a factory pattern on Concordium is complicated by the fact that one
7777
smart contract instance cannot create other smart contract instances programmatically.
7878
On Concordium, every smart contract instance is created by a top-level transaction. To achieve
79-
something resembling the factory pattern, we have to separately create the new product instances
80-
in an uninitialized state, and then have the factory initialize them.
79+
something resembling the factory pattern, the product instances must first be created in an
80+
uninitialized state, and then the factory must be invoked to initialize them in a separate step.
8181

82-
The sequence diagram below illustrates how the factory pattern is realised on Concordium.
83-
First of all, the user constructs a new instance of the ``product`` smart contract, invoking the
82+
The sequence diagram below illustrates how the factory pattern is realized on Concordium.
83+
First, the user constructs a new instance of the ``product`` smart contract, invoking the
8484
``init_product`` constructor.
8585
This creates the ``Product`` contract in a special uninitialized state.
8686
The user then invokes the ``produce`` endpoint on the ``Factory`` contract, passing in a reference
@@ -91,7 +91,7 @@ the ``Product`` being fully initialized.
9191
.. image:: images/concordium-factory.svg
9292
:alt: sequence diagram showing how the factory pattern might be realised on Concordium
9393

94-
This process is significantly more complex than the idealised factory pattern we started with.
94+
This process is significantly more complex than the original idealized factory pattern.
9595
In particular, the user is required to sign two separate transactions, with the second one depending
9696
on the result of the first one. If this two-step process is presented to the end user, it is likely
9797
to cause confusion. Additionally, two-step process means that the smart contracts have to be robust
@@ -112,7 +112,7 @@ If the factory does not maintain an on-going relationship with the product,
112112
then it is generally possible to simply initialize the product entirely in
113113
the constructor (``init_product``), removing the separate ``initialize`` operation entirely.
114114
In general, if the factory contract does not need to update its state
115-
after initializing the product, then it serves no purpose. It would be
115+
when initializing the product, then it serves no purpose. It would be
116116
sufficient for the user to construct the product directly in the same way as the factory
117117
would have.
118118

@@ -137,7 +137,7 @@ each product.
137137

138138
For instance, a CIS2 contract can manage multiple NFTs that each have distinct token IDs.
139139
Rather than each NFT being its own contract instance (created by a factory), they are simply
140-
handled as part of the state of the overall CIS2 contract. Here, the token ID actts as a
140+
handled as part of the state of the overall CIS2 contract. Here, the token ID acts as a
141141
virtual address.
142142

143143
The main disadvantage of this approach is that the isolation between the states of each product
@@ -172,21 +172,21 @@ The ``produce`` endpoint
172172
^^^^^^^^^^^^^^^^^^^^^^^^
173173

174174
The ``produce`` method of the factory expects one parameter that is the address of an uninitialized
175-
instance of the ``product`` contract. First of all, the parameter is read from the context:
175+
instance of the ``product`` contract. First, the parameter is read from the context:
176176

177177
.. code-block:: Rust
178178
179179
let product_address = ctx.parameter_cursor().get()?;
180180
181-
The factory needs to be sure that the address does actually refer to an instance of the ``product``
181+
The factory needs to be sure that the address actually refers to an instance of the ``product``
182182
contract, in order to ensure correct behavior. This can be achieved by checking the module reference
183183
and contract name against expected values. Together, the module reference and contract name uniquely
184184
identify the code of the smart contract instance.
185185

186186
Getting the module reference and contract name is done using the host functions
187-
``contract_module_reference`` and ``contract_name``, respectively. Note: both of these functions
188-
are introduced in protocol version 7, and will not work while the chain is running an earlier
189-
protocol version.
187+
``contract_module_reference`` and ``contract_name``, respectively. **Note: both of these functions**
188+
**are introduced in protocol version 7, and will not work while the chain is running an earlier**
189+
**protocol version.**
190190

191191
In this example, the factory and product contracts are defined in the same module.
192192
Thus, to check that the module reference of the product is correct, it is sufficient to check that
@@ -208,23 +208,32 @@ module rereference for the product is to be determined later, it could be passed
208208
when creating the ``factory`` instance.
209209

210210
If the module defining the product is known to only contain one smart contract, then checking the
211-
module reference is sufficient for identifying the code of the product smart contract. In this case,
212-
however, both the factory and product contracts are defined in the same module, so it is necessary
213-
to also check the contract name. This is achieved as follows:
211+
module reference is sufficient for identifying the code of the product smart contract. In the example smart contract,
212+
however, both the factory and product contracts are defined in the same module, so it is also necessary
213+
to check the contract name. This is achieved as follows:
214214

215215
.. code-block:: Rust
216216
217217
let product_name =
218218
host.contract_name(product_address).or(Err(FactoryError::NonExistentProduct))?;
219219
ensure_eq!(product_name, PRODUCT_INIT_NAME, FactoryError::InvalidProduct);
220220
221-
Now the contract is known to be an instance of ``product``, the next step is to call
222-
``initialize``. In this example, ``initialize`` takes as a parameter that is the index assigned to
223-
it, which will be the current value of ``next_product`` in the state.
221+
Now the contract is known to be an instance of ``product``, the next step is to update the state of
222+
the factory contract:
223+
224+
.. code-block:: Rust
225+
226+
let state = host.state_mut();
227+
let next_product = state.next_product;
228+
state.next_product = next_product + 1;
229+
state.products.insert(next_product, product_address);
230+
231+
Finally, it remains to invoke ``initialize`` on the product.
232+
In this example, ``initialize`` takes a parameter that is the index assigned to
233+
it, which will be the old value of ``next_product`` in the state.
224234

225235
.. code-block:: Rust
226236
227-
let next_product = host.state().next_product;
228237
host.invoke_contract(
229238
&product_address,
230239
&next_product,
@@ -235,13 +244,6 @@ it, which will be the current value of ``next_product`` in the state.
235244
236245
Here, it is assumed that ``initialize`` will fail, for instance, if it is called on a product that
237246
has previously been initialized.
238-
It only remains to update the factory contract's state:
239-
240-
.. code-block:: Rust
241-
242-
let state = host.state_mut();
243-
state.next_product = next_product + 1;
244-
state.products.insert(next_product, product_address);
245247

246248
The ``product`` contract
247249
------------------------
@@ -295,15 +297,15 @@ initialized:
295297
Since the construction and initialization of the product occur in two
296298
separate transactions, it is possible that a third party might try to hijack
297299
the process by inserting their own transaction to initialize the product.
298-
For instance, an adversary could invoke a different factory instance than indended by the user,
300+
For instance, an adversary could invoke a different factory instance than intended by the user,
299301
as illustrated in the following sequence diagram:
300302

301303
.. image:: images/factory-adversary.svg
302304
:alt: sequence diagram showing how a third party might hijack a product
303305

304306
To prevent this possibility, the product checks in its ``initialize`` method that the invoker of the
305-
transaction (i.e. the account that originated the transaction as a whole) is the same account as
306-
created the product contract instance (i.e. the "owner"):
307+
transaction (i.e., the account that originated the transaction as a whole) is the same account as
308+
created the product contract instance (i.e., the "owner"):
307309

308310
.. code-block:: Rust
309311
@@ -319,7 +321,7 @@ the invoker Adversary does not match the owner User), but success for User:
319321
Typically, it is wrong to use the invoker of a transaction for
320322
authorization, rather than the immediate caller. For instance, a user might
321323
invoke some untrusted smart contract, and expect it is not authorized to
322-
transfer tokens she holds on another contract. If the token-holding contract
324+
transfer tokens they hold on another contract. If the token-holding contract
323325
used the invoker for authorization, then the untrusted contract could
324326
transfer the tokens. In the case of the factory pattern, however, the
325327
authorization is for a one-time use (initializing the product contract)
@@ -331,7 +333,7 @@ the invoker Adversary does not match the owner User), but success for User:
331333
.. image:: images/factory-tricked.svg
332334
:alt: sequence diagram showing how a hijacking attempt may succeed if the user is deceived into signing a bad transaction
333335

334-
This is hopefully unlikely. Moreover, the effect of
336+
Hopefully, this is unlikely. Moreover, the effect of
335337
such a hijacking should typically be that the product cannot be used as the
336338
user intended, but the user would still be able to create another product
337339
and have the factory produce that correctly.
@@ -362,7 +364,7 @@ At this point, it just remains to initialize the state of the product:
362364
This is because ``initialize`` checks that the immediate caller is a smart contract, and
363365
records the contract address in the state. The fact that this prevents a user from directly
364366
invoking ``initialize`` is incidental. The design intent is not to prevent the user from
365-
badly initializing products (which they could also do by invoking a "BadFactory" as previously
367+
initializing products badly (which they could also do by invoking a "BadFactory" as previously
366368
noted). The intent is that products that are produced by the factory are produced correctly.
367369

368370

@@ -379,7 +381,7 @@ product before `initialize` is called, then the consequences and risk of
379381
hijacking are more sever. Thus, to adhere to the factory pattern, the
380382
product contract must:
381383

382-
1. Always be constructed in an uninitialized state, with no balance, authority or any other state.
384+
1. Always be constructed in an uninitialized state, with no balance, authority, or any other state.
383385

384386
2. Only permit the ``initialize`` update operation while it is in the uninitialized state.
385387

source/mainnet/smart-contracts/onboarding-guide-ethereum-developers/faq.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,10 @@ Deploying and initializing smart contracts
625625

626626
.. dropdown:: Can I create a factory smart contract on Concordium?
627627

628-
No. A factory smart contract on the Ethereum chain deploys other smart contracts. In contrast,
628+
In short: no. A factory smart contract on the Ethereum chain deploys other smart contracts. In contrast,
629629
the ``init`` function has to be called by an account (not a smart contract) on the Concordium chain.
630+
The guide to :ref:`using the factory pattern on Concordium<factory-pattern>` discusses
631+
the alternatives and how to emulate the factory pattern.
630632

631633
.. dropdown:: Can I predict/calculate the address of the smart contract before deploying it? Is there something similar to the Ethereum CREATE2?
632634

0 commit comments

Comments
 (0)