Skip to content

Commit b94ba99

Browse files
cptarturpiotmag769
andauthored
Add prepare cheatcode design document (#387)
<!-- Reference any GitHub issues resolved by this PR --> Closes #63 ## Introduced changes <!-- A brief description of the changes --> - Add design document discussing and proposing a solution in place of `prepare` cheaatcode. ## Breaking changes <!-- List of all breaking changes, if applicable --> ## Checklist <!-- Make sure all of these are complete --> - [x] Linked relevant issue - [x] Updated relevant documentation - [x] Added relevant tests - [x] Performed self-review of the code - [x] Added changes to `CHANGELOG.md` --------- Co-authored-by: Piotr Magiera <[email protected]>
1 parent a100066 commit b94ba99

File tree

1 file changed

+202
-0
lines changed

1 file changed

+202
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# `prepare` Cheatcode
2+
3+
<!-- TOC -->
4+
* [`prepare` Cheatcode](#prepare-cheatcode)
5+
* [Context](#context)
6+
* [Goal](#goal)
7+
* [Considered Solutions](#considered-solutions)
8+
* [Require Calling the `prepare` Cheatcode Before Every Deployment](#require-calling-the-prepare-cheatcode-before-every-deployment)
9+
* [Introducing the `precalculate_address` Cheatcode](#introducing-the-precalculateaddress-cheatcode)
10+
* [Salt "Counter"](#salt-counter)
11+
* [`precalculate_address` Cheatcode](#precalculateaddress-cheatcode)
12+
* [Known Problems With This Solution](#known-problems-with-this-solution)
13+
* [Example Usage](#example-usage)
14+
* [Proposed Solution](#proposed-solution)
15+
* [`declare` Cheatcode](#declare-cheatcode)
16+
* [Salt "Counter"](#salt-counter-1)
17+
* [Known Problems With This Solution](#known-problems-with-this-solution-1)
18+
* [Example Usage](#example-usage-1)
19+
<!-- TOC -->
20+
21+
## Context
22+
23+
Some testing cases require knowing the address of the contract that will be deployed in advance.
24+
These include:
25+
26+
- Using cheatcodes in contract's constructor: This requires running the cheatcode before `deploy` is called.
27+
- Tests that depend on knowing the address of the contract that will be deployed.
28+
29+
Since `deploy` already performs a deployment of the contract identified just by `class_hash` it is impossible to know
30+
the address in advance with the current cheatcodes.
31+
32+
## Goal
33+
34+
Propose a solution that will allow knowing the address of a contract before the deployment.
35+
36+
## Considered Solutions
37+
38+
### Require Calling the `prepare` Cheatcode Before Every Deployment
39+
40+
This is similar to how the contract deployment worked in Protostar:
41+
42+
1. Call `declare` with the contract name. This returns `class_hash`.
43+
2. Call `prepare` with the `class_hash` and `calldata`. This returns `PreparedContract` struct.
44+
3. Call `deploy` with the `PreparedContract` struct. This returns the address.
45+
46+
With this approach, `PreparedContract` also included a `contract_address` field that could be used for applying the
47+
cheatcodes before the contract was deployed.
48+
49+
The problem with this approach is that it is very verbose and requires multiple steps:
50+
Just deploying the contracts is more frequent use case than applying cheatcodes before the deployment.
51+
Users would have to perform an often unnecessary extra step with every deployment.
52+
53+
### Introducing the `precalculate_address` Cheatcode
54+
55+
Introducing the `precalculate_address` cheatcode that would return the contract address of the contract that would be
56+
deployed with `deploy` cheatcode.
57+
58+
#### Salt "Counter"
59+
60+
Introduce an internal "counter" and use its value to salt the otherwise deterministic contract address.
61+
Every time the `deploy` is called, increment this counter, so subsequent calls of `deploy` with the same `class_hash`
62+
will yield different addresses.
63+
64+
#### `precalculate_address` Cheatcode
65+
66+
Introduce a cheatcode with the signature:
67+
68+
```cairo
69+
fn precalculate_address(prepared_contract: PreparedContract) -> ContractAddress;
70+
```
71+
72+
That will use the same method of calculating the contract address as `deploy` uses, utilizing the internal counter.
73+
This way the user will have an ability to know the address of the contract that will be deployed, and the current
74+
deployment flow will remain unchanged.
75+
76+
#### Known Problems With This Solution
77+
78+
For the address returned by `precalculate_address` to match the address from `deploy`, the user will have to
79+
call `precalculate_address` immediately before the deployment or at least before any other calls to `deploy` as the
80+
internal counter will then be incremented.
81+
82+
This could be remedied by having separate counters for all `class_hashe`es, but it will still remain a limiting factor.
83+
84+
#### Example Usage
85+
86+
```cairo
87+
mod HelloStarknet {
88+
// ...
89+
90+
#[constructor]
91+
fn constructor(ref self: ContractState) {
92+
let timestamp = starknet::get_block_timestamp();
93+
self.create_time.write(timestamp);
94+
}
95+
}
96+
97+
#[test]
98+
fn call_and_invoke() {
99+
// Declare the contract
100+
let class_hash = declare('HelloStarknet');
101+
102+
// Prepare contract for deployment
103+
let prepared = PreparedContract { class_hash: class_hash, constructor_calldata: @ArrayTrait::new() };
104+
105+
// Precalculate the address
106+
let contract_address = precalulucate_address(prepared);
107+
108+
// Warp the address
109+
start_warp(contract_address, 1234);
110+
111+
// Deploy with warped constructor
112+
let contract_address = deploy(prepared).unwrap();
113+
let dispatcher = IHelloStarknetDispatcher { contract_address };
114+
}
115+
```
116+
117+
In case the user does not need to apply cheatcodes to the constructor, the deployment flow remains as before, without
118+
any steps introduced.
119+
120+
```cairo
121+
#[test]
122+
fn call_and_invoke() {
123+
// Declare the contract
124+
let class_hash = declare('HelloStarknet');
125+
126+
// Prepare contract for deployment
127+
let prepared = PreparedContract { class_hash: class_hash, constructor_calldata: @ArrayTrait::new() };
128+
129+
// Deploy
130+
let contract_address = deploy(prepared).unwrap();
131+
let dispatcher = IHelloStarknetDispatcher { contract_address };
132+
}
133+
```
134+
135+
## Proposed Solution
136+
137+
Change the current deployment flow, so it can better facilitate precalculating of contract addresses.
138+
139+
### `declare` Cheatcode
140+
141+
Change the `declare` cheatcode signature to this:
142+
143+
```cairo
144+
struct ContractClass {
145+
class_hash: ClassHash
146+
// ...
147+
}
148+
149+
trait ContractClassTrait {
150+
fn precalculate_address(self: @ContractClass, constructor_calldata: @Array::<felt252>) -> ContractAddress;
151+
fn deploy(self: @ContractClass, constructor_calldata: @Array::<felt252>) -> Result::<ContractAddress, RevertedTransaction>;
152+
}
153+
154+
impl ContractClassTrait of ContractClassTrait {
155+
// ...
156+
}
157+
158+
fn declare(contract: felt252) -> ContractClass;
159+
```
160+
161+
And remove the `deploy` cheatcode entirely.
162+
163+
Both `precalculate_address` and `deploy` should use the same way of calculating the contract address.
164+
165+
### Salt "Counter"
166+
167+
Introduce the same salt counter as [discussed here](#salt-counter).
168+
This will allow deterministic address calculation and deploying of multiple instances of the same contract.
169+
170+
### Known Problems With This Solution
171+
172+
Same problems as [indicated here](#known-problems-with-this-solution) apply to Proposed Solution 2 as well.
173+
174+
### Example Usage
175+
176+
```cairo
177+
mod HelloStarknet {
178+
// ...
179+
180+
#[constructor]
181+
fn constructor(ref self: ContractState) {
182+
let timestamp = starknet::get_block_timestamp();
183+
self.create_time.write(timestamp);
184+
}
185+
}
186+
187+
#[test]
188+
fn call_and_invoke() {
189+
// Declare the contract
190+
let contract = declare('HelloStarknet');
191+
192+
// Precalculate the address
193+
let contract_address = contract.precalulucate_address(@ArrayTrait::new());
194+
195+
// Warp the address
196+
start_warp(contract_address, 1234);
197+
198+
// Deploy with warped constructor
199+
let contract_address = contract.deploy(@ArrayTrait::new()).unwrap();
200+
let dispatcher = IHelloStarknetDispatcher { contract_address };
201+
}
202+
```

0 commit comments

Comments
 (0)