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 all 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 @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- UDC preset contract (#919)
- ERC1155Component and ERC1155ReceiverComponent mixins (#941)
- ERC721ReceiverComponent documentation (#945)

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: 0x060a1eeded2f1633b2ee71b6ee3de0d4466198d4097f23c6ad62b6f11e8f9775
:ERC1155-class-hash: 0x0518be7d9fa527c78d6929bf9e638e9c98b6077722e27e9546cc4342e830386e
:EthAccountUpgradeable-class-hash: 0x0359eceac190c19d22e2831f3bd2ecde9f1b1d034d0790e5dd6b3c91651bda97
:UniversalDeployer-class-hash: 0x059d5025ffd1b8a0172ed2b87ec3aa14475c0eeb32c5a78f35eba5db29404c47

// 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;
62 changes: 62 additions & 0 deletions src/presets/universal_deployer.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts for Cairo v0.10.0 (presets/universal_deployer.cairo)

/// # UniversalDeployerContract Preset
///
/// The Universal Deployer Contract is a standardized generic factory of Starknet contracts.
#[starknet::contract]
mod UniversalDeployer {
use hash::{HashStateTrait, HashStateExTrait};
use openzeppelin::utils::universal_deployer::interface;
use poseidon::PoseidonTrait;
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,
from_zero: bool,
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,
from_zero: bool,
calldata: Span<felt252>
) -> ContractAddress {
let deployer: ContractAddress = get_caller_address();
let mut _salt: felt252 = salt;
if !from_zero {
let mut hash_state = PoseidonTrait::new();
_salt = hash_state.update_with(deployer).update_with(salt).finalize();
}

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

self
.emit(
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
ContractDeployed { address, deployer, from_zero, 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;
165 changes: 165 additions & 0 deletions src/tests/presets/test_universal_deployer.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use core::pedersen::pedersen;
use hash::{HashStateTrait, HashStateExTrait};
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 poseidon::PoseidonTrait;
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_from_zero() {
let udc = deploy_udc();
let from_zero = true;
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, from_zero, ERC20_CALLDATA());
assert_eq!(expected_addr, deployed_addr);

// Check event
assert_only_event_contract_deployed(
udc.contract_address,
deployed_addr,
CALLER(),
from_zero,
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_not_from_zero() {
let udc = deploy_udc();
let from_zero = false;
testing::set_contract_address(CALLER());

// Hash salt
let mut state = PoseidonTrait::new();
let hashed_salt = state.update_with(CALLER()).update_with(SALT).finalize();

// Check address
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, from_zero, ERC20_CALLDATA());
assert_eq!(expected_addr, deployed_addr);

// Check event
assert_only_event_contract_deployed(
udc.contract_address,
deployed_addr,
CALLER(),
from_zero,
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
//

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/blob/v2.6.3/crates/cairo-lang-runner/src/casm_run/contract_address.rs#L38-L57
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_only_event_contract_deployed(
contract: ContractAddress,
address: ContractAddress,
deployer: ContractAddress,
from_zero: 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, from_zero, 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.10.0 (utils/universal_deployer/interface.cairo)

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

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