Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to cairo v0.9.0 #364

Merged
merged 37 commits into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
48a6399
update access, account, and security tests
andrew-fleming Jun 15, 2022
1c49aef
update token tests
andrew-fleming Jun 16, 2022
c0a23f9
update proxy, finish proxiable test
andrew-fleming Jun 16, 2022
edab3e5
update upgradeables and proxy tests
andrew-fleming Jun 17, 2022
d0b6e9a
update upgradeble erc20 and tests
andrew-fleming Jun 17, 2022
b8d4e8c
update docs
andrew-fleming Jun 17, 2022
5f97411
update branch Merge branch 'main' of https://github.com/andrew-flemin…
andrew-fleming Jun 17, 2022
2c27b0c
fix sentence
andrew-fleming Jun 17, 2022
24d877b
remove comment
andrew-fleming Jun 17, 2022
26dceda
remove directives
andrew-fleming Jun 17, 2022
b3d00d4
change definitions to classes
andrew-fleming Jun 17, 2022
c4c6da7
update readme, remove error msg
andrew-fleming Jun 17, 2022
3d78b6c
add assert_revert_entry_point
andrew-fleming Jun 17, 2022
325a629
fix comment
andrew-fleming Jun 17, 2022
052f114
add assert_revert_entry_point
andrew-fleming Jun 17, 2022
c7674ba
add declaring contracts section
andrew-fleming Jun 17, 2022
d8af94e
add specificity to declared classes
andrew-fleming Jun 18, 2022
8ead78f
add assert_revert_entry_point to ToC
andrew-fleming Jun 18, 2022
e60441d
fix conflicts
andrew-fleming Jun 24, 2022
200518d
rebase
andrew-fleming Jun 24, 2022
bf1b226
Add RELEASING.md (#363)
martriay Jun 21, 2022
b447b72
update token tests
andrew-fleming Jun 16, 2022
d85072c
update proxy, finish proxiable test
andrew-fleming Jun 16, 2022
7792429
update upgradeables and proxy tests
andrew-fleming Jun 17, 2022
d7a35bd
add assert_revert_entry_point
andrew-fleming Jun 17, 2022
2ab7f5c
rebase
andrew-fleming Jun 24, 2022
d51e948
fix test name
andrew-fleming Jun 24, 2022
c55555b
fix mocksigner section
andrew-fleming Jun 24, 2022
cd56593
Apply suggestions from code review
andrew-fleming Jun 24, 2022
5b40716
Update docs/Proxies.md
andrew-fleming Jun 24, 2022
64ab3ef
change 'implementation instance'
andrew-fleming Jun 24, 2022
8873f3e
fix attribution to v0.2.0
andrew-fleming Jun 24, 2022
5a3255e
remove trailing commas
andrew-fleming Jun 24, 2022
882296c
change 'internal' comment
andrew-fleming Jun 24, 2022
5a99f5b
remove unnecessary admin check
andrew-fleming Jun 24, 2022
7874082
update branch
andrew-fleming Jun 24, 2022
f1a8fb5
fix proxy api
andrew-fleming Jun 24, 2022
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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,20 +226,20 @@ docker run cairo-tests
This repo utilizes the [pytest-xdist](https://pytest-xdist.readthedocs.io/en/latest/) plugin which runs tests in parallel. This feature increases testing speed; however, conflicts with a shared state can occur since tests do not run in order. To overcome this, independent cached versions of contracts being tested should be provisioned to each test case. Here's a simple fixture example:

```python
from utils import get_contract_def, cached_contract
from utils import get_contract_class, cached_contract

@pytest.fixture(scope='module')
def foo_factory():
# get contract definition
foo_def = get_contract_def('path/to/foo.cairo')
# get contract class
foo_cls = get_contract_class('path/to/foo.cairo')

# deploy contract
starknet = await Starknet.empty()
foo = await starknet.deploy(contract_def=foo_def)
foo = await starknet.deploy(contract_class=foo_cls)

# copy the state and cache contract
state = starknet.state.copy()
cached_foo = cached_contract(state, foo_def, foo)
cached_foo = cached_contract(state, foo_cls, foo)

return cached_foo
```
Expand Down
32 changes: 32 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Releasing

Releasing checklist:

(1) Write a changelog.

(2) Make sure to update SPDX license identifiers. For example:

```cairo
# SPDX-License-Identifier: MIT
# OpenZeppelin Contracts for Cairo v0.1.0 (account/Account.cairo)
```

to

```cairo
# SPDX-License-Identifier: MIT
# OpenZeppelin Contracts for Cairo v0.2.0 (account/Account.cairo)
```

(3) Create a release branch and add a tag to it. This branch can be useful if we need to push a hot fix on top of an existing release in the case of a bug.

```sh
git checkout -b release-0.2.0
git tag v0.2.0
```

(4) Push the tag to the main repository, [triggering the CI and release process](https://github.com/OpenZeppelin/cairo-contracts/blob/b27101eb826fae73f49751fa384c2a0ff3377af2/.github/workflows/python-app.yml#L60).

```sh
git push origin v0.2.0
```
18 changes: 9 additions & 9 deletions docs/Account.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A more detailed writeup on the topic can be found on [Perama's blogpost](https:/
* [Standard Interface](#standard-interface)
* [Keys, signatures and signers](#keys-signatures-and-signers)
* [Signer](#signer)
* [TestSigner utility](#TestSigner-utility)
* [MockSigner utility](#mocksigner-utility)
* [Account entrypoint](#account-entrypoint)
* [Call and AccountCallArray format](#call-and-accountcallarray-format)
* [Call](#call)
Expand Down Expand Up @@ -40,7 +40,7 @@ In Python, this would look as follows:

```python
from starkware.starknet.testing.starknet import Starknet
signer = TestSigner(123456789987654321)
signer = MockSigner(123456789987654321)
starknet = await Starknet.empty()

# 1. Deploy Account
Expand Down Expand Up @@ -116,29 +116,29 @@ Which returns:
* `sig_r` the transaction signature
* `sig_s` the transaction signature

While the `Signer` class performs much of the work for a transaction to be sent, it neither manages nonces nor invokes the actual transaction on the Account contract. To simplify Account management, most of this is abstracted away with `TestSigner`.
While the `Signer` class performs much of the work for a transaction to be sent, it neither manages nonces nor invokes the actual transaction on the Account contract. To simplify Account management, most of this is abstracted away with `MockSigner`.

### TestSigner utility
### MockSigner utility

The `TestSigner` class in [utils.py](../tests/utils.py) is used to perform transactions on a given Account, crafting the transaction and managing nonces.
The `MockSigner` class in [utils.py](../tests/utils.py) is used to perform transactions on a given Account, crafting the transaction and managing nonces.

The flow of a transaction starts with checking the nonce and converting the `to` contract address of each call to hexadecimal format. The hexadecimal conversion is necessary because Nile's `Signer` converts the address to a base-16 integer (which requires a string argument). Note that directly converting `to` to a string will ultimately result in an integer exceeding Cairo's `FIELD_PRIME`.

The values included in the transaction are passed to the `sign_transaction` method of Nile's `Signer` which creates and returns a signature. Finally, the `TestSigner` instance invokes the account contract's `__execute__` with the transaction data.
The values included in the transaction are passed to the `sign_transaction` method of Nile's `Signer` which creates and returns a signature. Finally, the `MockSigner` instance invokes the account contract's `__execute__` with the transaction data.

Users only need to interact with the following exposed methods to perform a transaction:

* `send_transaction(account, to, selector_name, calldata, nonce=None, max_fee=0)` returns a future of a signed transaction, ready to be sent.

* `send_transactions(account, calls, nonce=None, max_fee=0)` returns a future of batched signed transactions, ready to be sent.

To use `TestSigner`, pass a private key when instantiating the class:
To use `MockSigner`, pass a private key when instantiating the class:

```python
from utils import TestSigner
from utils import MockSigner

PRIVATE_KEY = 123456789987654321
signer = TestSigner(PRIVATE_KEY)
signer = MockSigner(PRIVATE_KEY)
```

Then send single transactions with the `send_transaction` method.
Expand Down
2 changes: 1 addition & 1 deletion docs/ERC20.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ erc20 = await starknet.deploy(
As most StarkNet contracts, it expects to be called by another contract and it identifies it through `get_caller_address` (analogous to Solidity's `this.address`). This is why we need an Account contract to interact with it. For example:

```python
signer = TestSigner(PRIVATE_KEY)
signer = MockSigner(PRIVATE_KEY)
amount = uint(100)

account = await starknet.deploy(
Expand Down
2 changes: 1 addition & 1 deletion docs/ERC721.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ erc721 = await starknet.deploy(
To mint a non-fungible token, send a transaction like this:

```python
signer = TestSigner(PRIVATE_KEY)
signer = MockSigner(PRIVATE_KEY)
tokenId = uint(1)

await signer.send_transaction(
Expand Down
109 changes: 63 additions & 46 deletions docs/Proxies.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,31 @@
* [Events](#events)
* [Using proxies](#using-proxies)
* [Contract upgrades](#contract-upgrades)
* [Declaring contracts](#declaring-contracts)
* [Handling method calls](#handling-method-calls)
* [Presets](#presets)

## Quickstart

The general workflow is:

1. deploy implementation contract
2. deploy proxy contract with the implementation contract's address set in the proxy's constructor calldata
3. initialize the implementation contract by sending a call to the proxy contract. This will redirect the call to the implementation contract and behave like the implementation contract's constructor
1. declare an implementation contract
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
2. deploy proxy contract with the implementation contract's class hash set in the proxy's constructor calldata
3. initialize the implementation contract by sending a call to the proxy contract. This will redirect the call to the implementation contract class and behave like the implementation contract's constructor

In Python, this would look as follows:

```python
# deploy implementation
IMPLEMENTATION = await starknet.deploy(
# declare implementation contract
IMPLEMENTATION = await starknet.declare(
"path/to/implementation.cairo",
constructor_calldata=[]
)

# deploy proxy
PROXY = await starknet.deploy(
"path/to/proxy.cairo",
constructor_calldata=[
IMPLEMENTATION.contract_address, # set implementation address
IMPLEMENTATION.class_hash, # set implementation contract class hash
]
)

Expand Down Expand Up @@ -74,7 +74,7 @@ The StarkNet compiler, meanwhile, already creates pseudo-random storage addresse

A proxy contract is a contract that delegates function calls to another contract. This type of pattern decouples state and logic. Proxy contracts store the state and redirect function calls to an implementation contract that handles the logic. This allows for different patterns such as upgrades, where implementation contracts can change but the proxy contract (and thus the state) does not; as well as deploying multiple proxy instances pointing to the same implementation. This can be useful to deploy many contracts with identical logic but unique initialization data.

In the case of contract upgrades, it is achieved by simply changing the proxy's reference to the implementation contract. This allows developers to add features, update logic, and fix bugs without touching the state or the contract address to interact with the application.
In the case of contract upgrades, it is achieved by simply changing the proxy's reference to the class hash of the declared implementation. This allows developers to add features, update logic, and fix bugs without touching the state or the contract address to interact with the application.

### Proxy contract

Expand All @@ -84,7 +84,7 @@ The [Proxy contract](../src/openzeppelin/upgrades/Proxy.cairo) includes two core

2. The `__l1_default__` method is also a fallback method; however, it redirects the function call and associated calldata to a layer one contract. In order to invoke `__l1_default__`, the original function call must include the library function `send_message_to_l1`. See Cairo's [Interacting with L1 contracts](https://www.cairo-lang.org/docs/hello_starknet/l1l2.html) for more information.

Since this proxy is designed to work both as an [UUPS-flavored upgrade proxy](https://eips.ethereum.org/EIPS/eip-1822) as well as a non-upgradeable proxy, it does not know how to handle its own state. Therefore it requires the implementation contract to be deployed beforehand, so its address can be passed to the Proxy on construction time.
Since this proxy is designed to work both as an [UUPS-flavored upgrade proxy](https://eips.ethereum.org/EIPS/eip-1822) as well as a non-upgradeable proxy, it does not know how to handle its own state. Therefore it requires the implementation contract class to be declared beforehand, so its class hash can be passed to the Proxy on construction time.

When interacting with the contract, function calls should be sent by the user to the proxy. The proxy's fallback function redirects the function call to the implementation contract to execute.

Expand All @@ -104,7 +104,8 @@ If the implementation is upgradeable, it should:

The implementation contract should NOT:

* deploy with a traditional constructor (decorated with `@constructor`). Instead, use an initializer method that invokes the Proxy `constructor`.
* deploy like a regular contract. Instead, the implementation contract should be declared (which creates an instance containing its declared class and abi)
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
* set its initial state with a traditional constructor (decorated with `@constructor`). Instead, use an initializer method that invokes the Proxy `constructor`.

> Note that the Proxy `constructor` includes a check the ensures the initializer can only be called once; however, `_set_implementation` does not include this check. It's up to the developers to protect their implementation contract's upgradeability with access controls such as [`assert_only_admin`](#assert_only_admin).

Expand All @@ -120,19 +121,19 @@ For a full implementation contract example, please see:
func constructor(proxy_admin: felt):
end

func _set_implementation(new_implementation: felt):
func assert_only_admin():
end

func _set_admin(new_admin: felt):
func get_implementation_hash() -> (implementation: felt):
end

func get_implementation() -> (implementation: felt):
func get_admin() -> (admin: felt):
end

func get_admin() -> (admin: felt):
func _set_admin(new_admin: felt):
end

func assert_only_admin():
func _set_implementation_hash(new_implementation: felt):
end
```

Expand All @@ -150,37 +151,21 @@ Returns:

None.

#### `_set_implementation`
#### `assert_only_admin`

Sets the implementation contract. This method is included in the proxy contract's constructor and is furthermore used to upgrade contracts.
Throws if called by any account other than the admin.
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved

Parameters:

```cairo
new_implementation: felt
```

Returns:

None.

#### `_set_admin`

Sets the admin of the proxy contract.

Parameters:

```cairo
new_admin: felt
```

Returns:

None.

#### `get_implementation`

Returns the current implementation address.
Returns the current implementation hash.

Parameters:

Expand All @@ -206,14 +191,30 @@ Returns:
admin: felt
```

#### `assert_only_admin`
#### `_set_admin`

Throws if called by any account other than the admin.
Sets the admin of the proxy contract.
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved

Parameters:

```cairo
new_admin: felt
```

Returns:

None.

#### `_set_implementation_hash`

Sets the implementation contract class. This method is included in the proxy contract's constructor and is furthermore used to upgrade contracts.
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved

Parameters:

```cairo
new_implementation: felt
```

Returns:

None.
Expand All @@ -223,49 +224,61 @@ None.
```cairo
func Upgraded(implementation: felt):
end

func AdminChanged(oldAdmin: felt, newAdmin: felt):
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
end
```

#### `Upgraded`

Emitted when a proxy contract sets a new implementation address.
Emitted when a proxy contract sets a new implementation class hash.

Parameters:

```cairo
implementation: felt
```

#### `AdminChanged`

Emitted when the `admin` is changed.
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved

Parameters:

```cairo
oldAdmin: felt
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
newAdmin: felt
```

## Using proxies

### Contract upgrades

To upgrade a contract, the implementation contract should include an `upgrade` method that, when called, changes the reference to a new deployed contract like this:

```python
# deploy first implementation
IMPLEMENTATION = await starknet.deploy(
# declare first implementation
IMPLEMENTATION = await starknet.declare(
"path/to/implementation.cairo",
constructor_calldata=[]
)

# deploy proxy
PROXY = await starknet.deploy(
"path/to/proxy.cairo",
constructor_calldata=[
IMPLEMENTATION.contract_address, # set implementation address
IMPLEMENTATION.class_hash, # set implementation hash
]
)

# deploy implementation v2
IMPLEMENTATION_V2 = await starknet.deploy(
# declare implementation v2
IMPLEMENTATION_V2 = await starknet.declare(
"path/to/implementation_v2.cairo",
constructor_calldata=[]
)

# call upgrade with the new implementation contract address
# call upgrade with the new implementation contract class hash
await signer.send_transaction(
account, PROXY.contract_address, 'upgrade', [
IMPLEMENTATION_V2.contract_address
IMPLEMENTATION_V2.class_hash
]
)
```
Expand All @@ -275,6 +288,10 @@ For a full deployment and upgrade implementation, please see:
* [Upgrades V1](../tests/mocks/upgrades_v1_mock.cairo)
* [Upgrades V2](../tests/mocks/upgrades_v2_mock.cairo)

### Declaring contracts

StarkNet contracts come in two forms: contract classes and contract instances. Contract classes represent the uninstantiated, stateless code; whereas, contract instances are instantiated and include the state. Since the Proxy contract references the implementation contract by its class hash, declaring an implementation contract proves sufficient (as opposed to a full deployment). For more information on declaring classes, see [StarkNet's documentation](https://starknet.io/docs/hello_starknet/intro.html#declare-contract).

### Handling method calls

As with most StarkNet contracts, interacting with a proxy contract requires an [account abstraction](../docs/Account.md#quickstart). One notable difference with proxy contracts versus other contract implementations is that calling `@view` methods also requires an account abstraction. As of now, direct calls to default entrypoints are only supported by StarkNet's `syscalls` from other contracts i.e. account contracts. The differences in getter methods written in Python, for example, are as follows:
Expand Down
Loading