Skip to content

Commit

Permalink
Migrate UDC (#919)
Browse files Browse the repository at this point in the history
* add udc preset

* add udc preset class hash

* tidy up code

* add entry to changelog

* tidy up code

* clean up code

* add udc impl

* add deployment check and comments

* Apply suggestions from code review

Co-authored-by: Eric Nordelo <[email protected]>

* add comments

* move IUniversalDeployer to interface mod in udc dir

* fix conflicts, add PartialEq to udc event

* update changelog

* remove duplicate entry

* Apply suggestions from code review

Co-authored-by: Eric Nordelo <[email protected]>

* fix from_zero logic

* remove member names from struct

* abstract event assertions into fn

* fix formatting

* Update src/tests/presets/test_universal_deployer.cairo

Co-authored-by: Eric Nordelo <[email protected]>

* update assertion fn

* Update src/presets/universal_deployer.cairo

Co-authored-by: Eric Nordelo <[email protected]>

* use poseidon in udc

* fix formatting

* fix spdx

* Apply suggestions from code review

Co-authored-by: Martín Triay <[email protected]>

* fix from_zero conflicts, change poseidon use, update tests

* fix formatting

* remove deployment info from comment

* change code reference in comment

* Apply suggestions from code review

Co-authored-by: Martín Triay <[email protected]>

* fix formatting

* fix from_zero var in event test

* Apply suggestions from code review

Co-authored-by: Eric Nordelo <[email protected]>

* update HashTrait in test

---------

Co-authored-by: Eric Nordelo <[email protected]>
Co-authored-by: Martín Triay <[email protected]>
  • Loading branch information
3 people authored Mar 22, 2024
1 parent e039e86 commit ec44ea8
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 0 deletions.
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(
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;
}

0 comments on commit ec44ea8

Please sign in to comment.