-
Notifications
You must be signed in to change notification settings - Fork 359
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
e039e86
commit ec44ea8
Showing
10 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ mod test_erc1155; | |
mod test_erc20; | ||
mod test_erc721; | ||
mod test_eth_account; | ||
mod test_universal_deployer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
|
||
mod selectors; | ||
mod serde; | ||
mod universal_deployer; | ||
mod unwrap_and_cast; | ||
|
||
use starknet::ContractAddress; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
mod interface; | ||
use interface::IUniversalDeployer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |