diff --git a/src/openzeppelin/tests.cairo b/src/openzeppelin/tests.cairo index 0110b5abc..931e80b47 100644 --- a/src/openzeppelin/tests.cairo +++ b/src/openzeppelin/tests.cairo @@ -4,6 +4,7 @@ mod introspection; mod account; mod token; mod test_initializable; +mod test_udc; mod test_pausable; mod mocks; mod utils; diff --git a/src/openzeppelin/tests/test_udc.cairo b/src/openzeppelin/tests/test_udc.cairo new file mode 100644 index 000000000..3b64b8b63 --- /dev/null +++ b/src/openzeppelin/tests/test_udc.cairo @@ -0,0 +1,96 @@ +use array::ArrayTrait; +use core::result::ResultTrait; +use core::traits::Into; +use debug::PrintTrait; +use option::OptionTrait; +use starknet::class_hash::ClassHash; +use starknet::class_hash::Felt252TryIntoClassHash; +use starknet::ContractAddress; +use starknet::contract_address_const; +use traits::TryInto; +use zeroable::Zeroable; +use super::utils; + +use openzeppelin::utils::universal_deployer::UniversalDeployer; +use openzeppelin::utils::universal_deployer::IUniversalDeployerDispatcher; +use openzeppelin::utils::universal_deployer::IUniversalDeployerDispatcherTrait; + +use openzeppelin::token::erc20::ERC20; +use openzeppelin::token::erc20::interface::IERC20Dispatcher; +use openzeppelin::token::erc20::interface::IERC20DispatcherTrait; + +const RAW_SALT: felt252 = 123456789; + +fn ACCOUNT() -> ContractAddress { + contract_address_const::<10>() +} + +fn deploy_udc() -> IUniversalDeployerDispatcher { + let calldata = ArrayTrait::::new(); + let (address, _) = starknet::deploy_syscall( + UniversalDeployer::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + ) + .unwrap(); + + IUniversalDeployerDispatcher { contract_address: address } +} + +#[test] +#[available_gas(2000000)] +fn test_deploy_not_unique() { + let udc = deploy_udc(); + + // udc args + let erc20_class_hash = ERC20::TEST_CLASS_HASH.try_into().unwrap(); + let unique = false; + + // calldata (erc20 args) + let name = 0; + let symbol = 0; + let initial_supply = 1000_u256; + let recipient = contract_address_const::<0x123>(); + let mut calldata = ArrayTrait::::new(); + calldata.append(name); + calldata.append(symbol); + calldata.append(initial_supply.low.into()); + calldata.append(initial_supply.high.into()); + calldata.append(recipient.into()); + + let expected_addr = utils::calculate_contract_address_from_hash( + RAW_SALT, erc20_class_hash, calldata.span(), Zeroable::zero() + ); + + let deployed_addr = udc.deploy_contract(erc20_class_hash, RAW_SALT, unique, calldata.span()); +/// assert(deployed_addr == expected_addr, 'Deployed address != expected'); +} + +#[test] +#[available_gas(2000000)] +fn test_deploy_unique() { + let udc = deploy_udc(); + + // udc args + let erc20_class_hash = ERC20::TEST_CLASS_HASH.try_into().unwrap(); + let unique = true; + + // calldata (erc20 args) + let name = 0; + let symbol = 0; + let initial_supply = 1000_u256; + let recipient = contract_address_const::<0x123>(); + let mut calldata = ArrayTrait::::new(); + calldata.append(name); + calldata.append(symbol); + calldata.append(initial_supply.low.into()); + calldata.append(initial_supply.high.into()); + calldata.append(recipient.into()); + + let hashed_salt = pedersen(ACCOUNT().into(), RAW_SALT); + let expected_addr = utils::calculate_contract_address_from_hash( + hashed_salt, erc20_class_hash, calldata.span(), udc.contract_address + ); + + let deployed_addr = udc.deploy_contract(erc20_class_hash, hashed_salt, unique, calldata.span()); +/// assert(deployed_addr == expected_addr, 'Deployed address != expected'); +} + diff --git a/src/openzeppelin/tests/utils.cairo b/src/openzeppelin/tests/utils.cairo index d3b720cce..b0bd67597 100644 --- a/src/openzeppelin/tests/utils.cairo +++ b/src/openzeppelin/tests/utils.cairo @@ -1,10 +1,20 @@ use array::ArrayTrait; +use array::SpanTrait; use core::result::ResultTrait; +use core::traits::Into; +use hash::pedersen; use option::OptionTrait; use starknet::class_hash::Felt252TryIntoClassHash; +use starknet::class_hash::ClassHash; use starknet::ContractAddress; use traits::TryInto; +// from_bytes(b"STARKNET_CONTRACT_ADDRESS") +const CONTRACT_ADDRESS_PREFIX: felt252 = 0x535441524b4e45545f434f4e54524143545f41444452455353; +// 2**251 - 256 +const L2_ADDRESS_UPPER_BOUND: felt252 = + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00; + fn deploy(contract_class_hash: felt252, calldata: Array) -> ContractAddress { let (address, _) = starknet::deploy_syscall( contract_class_hash.try_into().unwrap(), 0, calldata.span(), false @@ -12,3 +22,42 @@ fn deploy(contract_class_hash: felt252, calldata: Array) -> ContractAdd .unwrap(); address } + +fn compute_hash_on_elements(mut data: Span::) -> felt252 { + let data_len: usize = data.len(); + let mut hash = 0; + loop { + match data.pop_front() { + Option::Some(x) => { + hash = pedersen(hash, *x); + }, + Option::None(_) => { + hash = pedersen(hash, data_len.into()); + break (); + }, + }; + }; + hash +} + +fn calculate_contract_address_from_hash( + salt: felt252, + class_hash: ClassHash, + constructor_calldata: Span::, + deployer_address: ContractAddress +) -> ContractAddress { + let constructor_calldata_hash = compute_hash_on_elements(constructor_calldata); + + let mut data = ArrayTrait::new(); + data.append(CONTRACT_ADDRESS_PREFIX); + data.append(deployer_address.into()); + data.append(salt); + data.append(class_hash.into()); + data.append(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() +} diff --git a/src/openzeppelin/utils.cairo b/src/openzeppelin/utils.cairo index e8d356c77..a46ac1b62 100644 --- a/src/openzeppelin/utils.cairo +++ b/src/openzeppelin/utils.cairo @@ -2,6 +2,7 @@ mod constants; mod selectors; mod serde; mod unwrap_and_cast; +mod universal_deployer; use array::ArrayTrait; use array::SpanTrait; diff --git a/src/openzeppelin/utils/universal_deployer.cairo b/src/openzeppelin/utils/universal_deployer.cairo new file mode 100644 index 000000000..b9c9fedc2 --- /dev/null +++ b/src/openzeppelin/utils/universal_deployer.cairo @@ -0,0 +1,56 @@ +use array::SpanTrait; +use option::OptionTrait; +use starknet::class_hash::ClassHash; +use starknet::ContractAddress; +use openzeppelin::utils::serde::SpanSerde; + +#[abi] +trait IUniversalDeployer { + fn deploy_contract( + class_hash: ClassHash, salt: felt252, unique: bool, _calldata: Span + ) -> ContractAddress; +} + +#[contract] +mod UniversalDeployer { + use super::SpanSerde; + + use array::SpanTrait; + use hash::pedersen; + use starknet::class_hash::ClassHash; + use starknet::ContractAddress; + use starknet::get_caller_address; + use traits::Into; + + #[event] + fn ContractDeployed( + address: ContractAddress, + deployer: ContractAddress, + unique: bool, + class_hash: ClassHash, + _calldata: Span, + salt: felt252, + ) {} + + #[external] + fn deploy_contract( + class_hash: ClassHash, salt: felt252, unique: bool, _calldata: Span + ) -> ContractAddress { + let deployer: ContractAddress = get_caller_address(); + + // Defaults for non-unique deployment + let mut _salt: felt252 = salt; + let mut from_zero: bool = true; + + if unique { + _salt = pedersen(deployer.into(), salt); + from_zero = false; + } + + let (address, _) = starknet::deploy_syscall(class_hash, _salt, _calldata, from_zero) + .unwrap_syscall(); + + ContractDeployed(address, deployer, unique, class_hash, _calldata, salt); + return address; + } +}