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

Migrate UDC #919

Merged
merged 41 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5894303
add udc preset
andrew-fleming Feb 18, 2024
48a72ac
add udc preset class hash
andrew-fleming Feb 18, 2024
f094ff6
tidy up code
andrew-fleming Feb 21, 2024
e135b1a
add entry to changelog
andrew-fleming Feb 21, 2024
b078f70
tidy up code
andrew-fleming Feb 21, 2024
90b542f
clean up code
andrew-fleming Feb 21, 2024
476e058
add udc impl
andrew-fleming Feb 21, 2024
e78867c
add deployment check and comments
andrew-fleming Feb 21, 2024
b343394
Apply suggestions from code review
andrew-fleming Feb 23, 2024
5215030
add comments
andrew-fleming Feb 24, 2024
d81c544
move IUniversalDeployer to interface mod in udc dir
andrew-fleming Feb 24, 2024
40b293a
fix conflicts, add PartialEq to udc event
andrew-fleming Mar 6, 2024
c645eca
fix conflicts
andrew-fleming Mar 6, 2024
620d22b
update changelog
andrew-fleming Mar 6, 2024
5324daa
remove duplicate entry
andrew-fleming Mar 6, 2024
2bbb194
Apply suggestions from code review
andrew-fleming Mar 11, 2024
8b0bf42
fix from_zero logic
andrew-fleming Mar 11, 2024
fb741dc
remove member names from struct
andrew-fleming Mar 11, 2024
8ee0297
abstract event assertions into fn
andrew-fleming Mar 11, 2024
b6d1e3f
fix formatting
andrew-fleming Mar 11, 2024
fbe27e0
Update src/tests/presets/test_universal_deployer.cairo
andrew-fleming Mar 12, 2024
22e4779
update assertion fn
andrew-fleming Mar 12, 2024
64f04fe
fix conflicts
andrew-fleming Mar 14, 2024
1569954
Update src/presets/universal_deployer.cairo
andrew-fleming Mar 15, 2024
db5f13d
use poseidon in udc
andrew-fleming Mar 19, 2024
c1de6e0
fix formatting
andrew-fleming Mar 19, 2024
a924bf5
Merge branch 'main' into add-udc
andrew-fleming Mar 19, 2024
05a6fa2
fix spdx
andrew-fleming Mar 19, 2024
baa6a11
Merge branch 'add-udc' of https://github.com/andrew-fleming/cairo-con…
andrew-fleming Mar 19, 2024
f25a3c1
Apply suggestions from code review
andrew-fleming Mar 19, 2024
f38a5a6
fix from_zero conflicts, change poseidon use, update tests
andrew-fleming Mar 19, 2024
14fc5be
fix formatting
andrew-fleming Mar 19, 2024
8e474a1
remove deployment info from comment
andrew-fleming Mar 19, 2024
f8da011
change code reference in comment
andrew-fleming Mar 20, 2024
036500e
Apply suggestions from code review
andrew-fleming Mar 22, 2024
b1c86c5
fix formatting
andrew-fleming Mar 22, 2024
1aa212d
fix from_zero var in event test
andrew-fleming Mar 22, 2024
564f0c8
Apply suggestions from code review
andrew-fleming Mar 22, 2024
bca3301
update HashTrait in test
andrew-fleming Mar 22, 2024
7bfdef6
fix conflicts
andrew-fleming Mar 22, 2024
d91c61d
fix conflicts
andrew-fleming Mar 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ERC721Component functions and Storage member
- `InternalTrait::_set_base_uri` and `InternalTrait::_base_uri` to handle ByteArrays (#857)
- `ERC721_base_uri` Storage member to store the base URI (#857)
- UDC preset contract (#919)

### Changed

Expand Down
4 changes: 4 additions & 0 deletions docs/modules/ROOT/pages/presets.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
:erc721: xref:/api/erc721.adoc#ERC721[ERC721]
:erc1155: xref:/api/erc1155.adoc#ERC1155[ERC1155]
:eth-account-upgradeable: xref:/api/account.adoc#EthAccountUpgradeable[EthAccountUpgradeable]
:udc: https://github.com/starknet-io/starknet-docs/blob/v0.1.479/components/Starknet/modules/architecture_and_concepts/pages/Smart_Contracts/universal-deployer.adoc[UniversalDeployer]
:sierra-class-hashes: https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/class-hash[Sierra class hashes]
:starkli: https://book.starkli.rs/introduction[starkli]
:wizard: https://wizard.openzeppelin.com[Wizard for Cairo]
Expand Down Expand Up @@ -41,6 +42,9 @@ NOTE: Class hashes were computed using {class-hash-cairo-version}.

| `{eth-account-upgradeable}`
| `{EthAccountUpgradeable-class-hash}`

| `{udc}`
| `{UniversalDeployer-class-hash}`
|===

TIP: {starkli} class-hash command can be used to compute the class hash from a Sierra artifact.
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/utils/_class_hashes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
:ERC721-class-hash: 0x06b7c9efc5467c621f58d87995302d940a39b7217b5c5a7a55555c97cabf5cd8
:ERC1155-class-hash: 0x518be7d9fa527c78d6929bf9e638e9c98b6077722e27e9546cc4342e830386e
:EthAccountUpgradeable-class-hash: 0x0580fe510cf07255540abda6f9d29aa07cb8944db444bca59e1573904c269844
:UniversalDeployer-class-hash: 0x0548f35c7316b4dc5efdaa929fe83f1007c7e0ae24bec3045d47c94da00dd386

// Presets page
:presets-page: xref:presets.adoc[Sierra class hash]
2 changes: 2 additions & 0 deletions src/presets.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ mod erc1155;
mod erc20;
mod erc721;
mod eth_account;
mod universal_deployer;

use account::Account;
use erc1155::ERC1155;
use erc20::ERC20;
use erc721::ERC721;
use eth_account::EthAccountUpgradeable;
use universal_deployer::UniversalDeployer;
63 changes: 63 additions & 0 deletions src/presets/universal_deployer.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts for Cairo v0.9.0 (presets/universal_deployer.cairo)

/// # UniversalDeployer Preset
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
///
/// The Universal Deployer Contract is a singleton smart contract that wraps `deploy_syscall`
/// to expose it to any contract that doesn't implement it, such as account contracts.
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
///
/// This contract is already deployed at 0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf
/// on mainnet, testnets, and starknet-devnet.
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
/// This address may change in the future.
#[starknet::contract]
mod UniversalDeployer {
use core::pedersen::pedersen;
use openzeppelin::utils::universal_deployer::interface;
use starknet::ClassHash;
use starknet::ContractAddress;
use starknet::SyscallResultTrait;
use starknet::get_caller_address;

#[storage]
struct Storage {}

#[event]
#[derive(Drop, PartialEq, starknet::Event)]
enum Event {
ContractDeployed: ContractDeployed
}

#[derive(Drop, PartialEq, starknet::Event)]
struct ContractDeployed {
address: ContractAddress,
deployer: ContractAddress,
unique: bool,
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
class_hash: ClassHash,
calldata: Span<felt252>,
salt: felt252,
}

#[abi(embed_v0)]
impl UniversalDeployerImpl of interface::IUniversalDeployer<ContractState> {
fn deploy_contract(
ref self: ContractState,
class_hash: ClassHash,
salt: felt252,
unique: bool,
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
calldata: Span<felt252>
) -> ContractAddress {
let deployer: ContractAddress = get_caller_address();
let from_zero: bool = !unique;
let mut _salt: felt252 = salt;
if unique {
_salt = pedersen(deployer.into(), salt);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider using poseidon since is much cheaper? Maybe a UDC_v2?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huge +1 to UDC_v2 with poseidon

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, but note this UDC is already v2

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may need to be explicit in the name regarding the version. If people are using UDC already to predict addresses before deployment, getting confused about whether it uses pedersen or poseidon could be problematic, since the addresses would be different.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we update to Poseidon then on this PR? And should we add documentation about it and versions? In the starknet docsite we are not for example mentioning which hash algorithm is used, or how to precompute the addresses, and I think that's worth to mention.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made this an issue: #950

}

let (address, _) = starknet::deploy_syscall(class_hash, _salt, calldata, from_zero)
.unwrap_syscall();

self.emit(ContractDeployed { address, deployer, unique, class_hash, calldata, salt });
return address;
}
}
}
1 change: 1 addition & 0 deletions src/tests/presets.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ mod test_erc1155;
mod test_erc20;
mod test_erc721;
mod test_eth_account;
mod test_universal_deployer;
161 changes: 161 additions & 0 deletions src/tests/presets/test_universal_deployer.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use core::pedersen::pedersen;
use openzeppelin::presets::universal_deployer::UniversalDeployer::ContractDeployed;
use openzeppelin::presets::universal_deployer::UniversalDeployer;
use openzeppelin::tests::mocks::erc20_mocks::DualCaseERC20Mock;
use openzeppelin::tests::utils::constants::{NAME, SYMBOL, SUPPLY, SALT, CALLER, RECIPIENT};
use openzeppelin::tests::utils;
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
use openzeppelin::utils::serde::SerializedAppend;
use openzeppelin::utils::universal_deployer::interface::{
IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait
};
use starknet::ClassHash;
use starknet::ContractAddress;
use starknet::testing;


// 2**251 - 256
const L2_ADDRESS_UPPER_BOUND: felt252 =
0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00;
const CONTRACT_ADDRESS_PREFIX: felt252 = 'STARKNET_CONTRACT_ADDRESS';

fn ERC20_CLASS_HASH() -> ClassHash {
DualCaseERC20Mock::TEST_CLASS_HASH.try_into().unwrap()
}

fn ERC20_CALLDATA() -> Span<felt252> {
let mut calldata = array![];
calldata.append_serde(NAME());
calldata.append_serde(SYMBOL());
calldata.append_serde(SUPPLY);
calldata.append_serde(RECIPIENT());
calldata.span()
}

fn deploy_udc() -> IUniversalDeployerDispatcher {
let calldata = array![];
let address = utils::deploy(UniversalDeployer::TEST_CLASS_HASH, calldata);

IUniversalDeployerDispatcher { contract_address: address }
}

#[test]
fn test_deploy_not_unique() {
let udc = deploy_udc();
let unique = false;
testing::set_contract_address(CALLER());

// Check address
let expected_addr = calculate_contract_address_from_hash(
SALT, ERC20_CLASS_HASH(), ERC20_CALLDATA(), Zeroable::zero()
);
let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, unique, ERC20_CALLDATA());
assert_eq!(expected_addr, deployed_addr);

// Check event
assert_event_contract_deployed(
udc.contract_address,
deployed_addr,
CALLER(),
unique,
ERC20_CLASS_HASH(),
ERC20_CALLDATA(),
SALT
);

// Check deployment
let erc20 = IERC20Dispatcher { contract_address: deployed_addr };
let total_supply = erc20.total_supply();
assert_eq!(total_supply, SUPPLY);
}

#[test]
fn test_deploy_unique() {
let udc = deploy_udc();
let unique = true;
testing::set_contract_address(CALLER());

// Check address
let hashed_salt = pedersen(CALLER().into(), SALT);
let expected_addr = calculate_contract_address_from_hash(
hashed_salt, ERC20_CLASS_HASH(), ERC20_CALLDATA(), udc.contract_address
);
let deployed_addr = udc.deploy_contract(ERC20_CLASS_HASH(), SALT, unique, ERC20_CALLDATA());
assert_eq!(expected_addr, deployed_addr);

// Check event
assert_event_contract_deployed(
udc.contract_address,
deployed_addr,
CALLER(),
unique,
ERC20_CLASS_HASH(),
ERC20_CALLDATA(),
SALT
);

// Check deployment
let erc20 = IERC20Dispatcher { contract_address: deployed_addr };
let total_supply = erc20.total_supply();
assert_eq!(total_supply, SUPPLY);
}

//
// Helpers
//

/// See https://github.com/starkware-libs/cairo-lang/blob/v0.13.0/src/starkware/cairo/common/hash_state.py
fn compute_hash_on_elements(mut data: Span<felt252>) -> felt252 {
let data_len: usize = data.len();
let mut hash = 0;
loop {
match data.pop_front() {
Option::Some(elem) => { hash = pedersen(hash, *elem); },
Option::None => {
hash = pedersen(hash, data_len.into());
break;
},
};
};
hash
}

/// See https://github.com/starkware-libs/cairo-lang/blob/v0.13.0/src/starkware/starknet/core/os/contract_address/contract_address.py
fn calculate_contract_address_from_hash(
salt: felt252,
class_hash: ClassHash,
constructor_calldata: Span<felt252>,
deployer_address: ContractAddress
) -> ContractAddress {
let constructor_calldata_hash = compute_hash_on_elements(constructor_calldata);

let mut data = array![];
data.append_serde(CONTRACT_ADDRESS_PREFIX);
data.append_serde(deployer_address);
data.append_serde(salt);
data.append_serde(class_hash);
data.append_serde(constructor_calldata_hash);
let raw_address = compute_hash_on_elements(data.span());

// Felt modulo is discouraged, hence the conversion to u256
let u256_addr: u256 = raw_address.into() % L2_ADDRESS_UPPER_BOUND.into();
let felt_addr = u256_addr.try_into().unwrap();
starknet::contract_address_try_from_felt252(felt_addr).unwrap()
}

fn assert_event_contract_deployed(
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
contract: ContractAddress,
address: ContractAddress,
deployer: ContractAddress,
unique: bool,
class_hash: ClassHash,
calldata: Span<felt252>,
salt: felt252
) {
let event = utils::pop_log::<UniversalDeployer::Event>(contract).unwrap();
let expected = UniversalDeployer::Event::ContractDeployed(
ContractDeployed { address, deployer, unique, class_hash, calldata, salt }
);
assert!(event == expected);
utils::assert_no_events_left(contract);
}
1 change: 1 addition & 0 deletions src/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

mod selectors;
mod serde;
mod universal_deployer;
mod unwrap_and_cast;

use starknet::ContractAddress;
Expand Down
2 changes: 2 additions & 0 deletions src/utils/universal_deployer.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod interface;
use interface::IUniversalDeployer;
16 changes: 16 additions & 0 deletions src/utils/universal_deployer/interface.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts for Cairo v0.9.0 (utils/universal_deployer/interface.cairo)
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved

use starknet::ClassHash;
use starknet::ContractAddress;

#[starknet::interface]
trait IUniversalDeployer<TState> {
fn deploy_contract(
ref self: TState,
class_hash: ClassHash,
salt: felt252,
unique: bool,
calldata: Span<felt252>
) -> ContractAddress;
}