diff --git a/.github/workflows/label-prs.yml b/.github/workflows/label-prs.yml new file mode 100644 index 0000000..2f9b6dd --- /dev/null +++ b/.github/workflows/label-prs.yml @@ -0,0 +1,33 @@ +name: Label PRs for feat-token-extensions + +on: + pull_request: + branches: + - feat-token-extensions + +jobs: + tag-and-label: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Add label to PR + uses: actions/github-script@v7 + with: + script: | + github.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: ['feat-token-extensions'] + }) + + - name: Add Tag to PR Title + run: | + gh pr edit ${{ github.event.pull_request.number }} --add-label "feat-token-extensions" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..149b429 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +scripts/node_modules +scripts/.env \ No newline at end of file diff --git a/README.md b/README.md index 714e024..bd38b15 100644 --- a/README.md +++ b/README.md @@ -137,4 +137,23 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! + + +## 🪛 Deployment + +This section details the steps to deploy Hyperlane contracts on Starknet. Note that the deployment script will set a basic configuration for all the required contracts. Further configuration process might be required based on the use case. Constructors parameters can be specified in the `contract_config.json`. +Firstly, set the following environment variables, important for the deployment process: +```bash +STARKNET_RPC_URL= +ACCOUNT_ADDRESS= +BENEFICIARY_ADDRESS= +NETWORK= +PRIVATE_KEY= +``` +The beneficiary address is the account that will be used to recover funds from the protocol fee. +Once set, the contracts can be deployed using this command( assuming `ts-node` is installed): + +```bash +ts-node deploy.ts +``` \ No newline at end of file diff --git a/cairo/Scarb.lock b/cairo/Scarb.lock index c9983c6..90ad413 100644 --- a/cairo/Scarb.lock +++ b/cairo/Scarb.lock @@ -4,7 +4,7 @@ version = 1 [[package]] name = "alexandria_bytes" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#b5c8356ce7d46a3665e08a8016d5abc02d9b0fe2" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70#bcdca70afdf59c9976148e95cebad5cf63d75a7f" dependencies = [ "alexandria_data_structures", "alexandria_math", @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "alexandria_data_structures" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#b5c8356ce7d46a3665e08a8016d5abc02d9b0fe2" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70#bcdca70afdf59c9976148e95cebad5cf63d75a7f" dependencies = [ "alexandria_encoding", ] @@ -21,7 +21,7 @@ dependencies = [ [[package]] name = "alexandria_encoding" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#b5c8356ce7d46a3665e08a8016d5abc02d9b0fe2" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70#bcdca70afdf59c9976148e95cebad5cf63d75a7f" dependencies = [ "alexandria_bytes", "alexandria_math", @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "alexandria_math" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#b5c8356ce7d46a3665e08a8016d5abc02d9b0fe2" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70#bcdca70afdf59c9976148e95cebad5cf63d75a7f" dependencies = [ "alexandria_data_structures", ] @@ -39,7 +39,7 @@ dependencies = [ [[package]] name = "alexandria_numeric" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#b5c8356ce7d46a3665e08a8016d5abc02d9b0fe2" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70#bcdca70afdf59c9976148e95cebad5cf63d75a7f" dependencies = [ "alexandria_math", "alexandria_searching", @@ -48,7 +48,7 @@ dependencies = [ [[package]] name = "alexandria_searching" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#b5c8356ce7d46a3665e08a8016d5abc02d9b0fe2" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70#bcdca70afdf59c9976148e95cebad5cf63d75a7f" dependencies = [ "alexandria_data_structures", ] @@ -56,7 +56,7 @@ dependencies = [ [[package]] name = "alexandria_storage" version = "0.3.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70#bcdca70afdf59c9976148e95cebad5cf63d75a7f" [[package]] name = "hyperlane_starknet" diff --git a/cairo/Scarb.toml b/cairo/Scarb.toml index 1fdf06a..3064581 100644 --- a/cairo/Scarb.toml +++ b/cairo/Scarb.toml @@ -9,8 +9,8 @@ cairo-version = "2.6.3" [dependencies] starknet = "2.6.3" -alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git" } -alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git" } +alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70" } +alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70" } openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.14.0" } diff --git a/cairo/src/contracts/client/router_component.cairo b/cairo/src/contracts/client/router_component.cairo index c783038..14cb35b 100644 --- a/cairo/src/contracts/client/router_component.cairo +++ b/cairo/src/contracts/client/router_component.cairo @@ -54,7 +54,7 @@ pub mod RouterComponent { } #[embeddable_as(RouterImpl)] - impl Router< + pub impl Router< TContractState, +HasComponent, +MailboxclientComponent::HasComponent, diff --git a/cairo/src/contracts/mocks/mock_hyp_erc721_uri_storage.cairo b/cairo/src/contracts/mocks/mock_hyp_erc721_uri_storage.cairo new file mode 100644 index 0000000..47ee231 --- /dev/null +++ b/cairo/src/contracts/mocks/mock_hyp_erc721_uri_storage.cairo @@ -0,0 +1,215 @@ +#[starknet::interface] +trait IMockHypERC721URIStorage { + fn set_token_uri(ref self: TContractState, token_id: u256, uri: ByteArray); +} + +#[starknet::contract] +pub mod MockHypERC721URIStorage { + use alexandria_bytes::{Bytes, BytesTrait}; + use hyperlane_starknet::contracts::client::gas_router_component::GasRouterComponent; + use hyperlane_starknet::contracts::client::mailboxclient_component::MailboxclientComponent; + use hyperlane_starknet::contracts::client::router_component::RouterComponent; + use hyperlane_starknet::contracts::token::components::erc721_uri_storage::ERC721URIStorageComponent; + use hyperlane_starknet::contracts::token::components::hyp_erc721_component::{ + HypErc721Component + }; + use hyperlane_starknet::contracts::token::components::token_router::{ + TokenRouterComponent, TokenRouterComponent::TokenRouterHooksTrait, + TokenRouterComponent::MessageRecipientInternalHookImpl, + TokenRouterTransferRemoteHookDefaultImpl + }; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; + use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + use starknet::{ContractAddress, get_caller_address}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: MailboxclientComponent, storage: mailboxclient, event: MailboxclientEvent); + component!(path: RouterComponent, storage: router, event: RouterEvent); + component!(path: GasRouterComponent, storage: gas_router, event: GasRouterEvent); + component!(path: TokenRouterComponent, storage: token_router, event: TokenRouterEvent); + component!(path: HypErc721Component, storage: hyp_erc721, event: HypErc721Event); + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!( + path: ERC721URIStorageComponent, storage: erc721_uri_storage, event: ERC721UriStorageEvent + ); + + // Ownable + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // MailboxClient + #[abi(embed_v0)] + impl MailboxClientImpl = + MailboxclientComponent::MailboxClientImpl; + impl MailboxClientInternalImpl = + MailboxclientComponent::MailboxClientInternalImpl; + + //Router + #[abi(embed_v0)] + impl RouterImpl = RouterComponent::RouterImpl; + impl RouterInternalImpl = RouterComponent::RouterComponentInternalImpl; + + // GasRouter + #[abi(embed_v0)] + impl GasRouterImpl = GasRouterComponent::GasRouterImpl; + + // TokenRouter + #[abi(embed_v0)] + impl TokenRouterImpl = TokenRouterComponent::TokenRouterImpl; + impl TokenRouterInternalImpl = TokenRouterComponent::TokenRouterInternalImpl; + + //HypERC721 + impl HypErc721InternalImpl = HypErc721Component::HypErc721InternalImpl; + + //ERC721 + #[abi(embed_v0)] + impl ERC721URIStorageImpl = + ERC721URIStorageComponent::ERC721URIStorageImpl; + #[abi(embed_v0)] + impl ERC721Impl = ERC721Component::ERC721Impl; + #[abi(embed_v0)] + impl ERC721CamelOnlyImpl = ERC721Component::ERC721CamelOnlyImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + impl ERC721URIStorageInternalImpl = + ERC721URIStorageComponent::ERC721URIStorageInternalImpl; + + //upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + mailboxclient: MailboxclientComponent::Storage, + #[substorage(v0)] + router: RouterComponent::Storage, + #[substorage(v0)] + gas_router: GasRouterComponent::Storage, + #[substorage(v0)] + token_router: TokenRouterComponent::Storage, + #[substorage(v0)] + hyp_erc721: HypErc721Component::Storage, + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + erc721_uri_storage: ERC721URIStorageComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + MailboxclientEvent: MailboxclientComponent::Event, + #[flat] + RouterEvent: RouterComponent::Event, + #[flat] + GasRouterEvent: GasRouterComponent::Event, + #[flat] + TokenRouterEvent: TokenRouterComponent::Event, + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + HypErc721Event: HypErc721Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + ERC721UriStorageEvent: ERC721URIStorageComponent::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, + mailbox: ContractAddress, + _mint_amount: u256, + _name: ByteArray, + _symbol: ByteArray, + _hook: ContractAddress, + _interchainSecurityModule: ContractAddress, + owner: ContractAddress, + ) { + self.ownable.initializer(owner); + self + .mailboxclient + .initialize(mailbox, Option::Some(_hook), Option::Some(_interchainSecurityModule)); + self.hyp_erc721.initialize(_mint_amount, _name, _symbol); + } + + #[abi(embed_v0)] + impl IMockHypERC721URIStorageImpl of super::IMockHypERC721URIStorage { + fn set_token_uri(ref self: ContractState, token_id: u256, uri: ByteArray) { + self.erc721_uri_storage._set_token_uri(token_id, uri); + } + } + + #[abi(embed_v0)] + impl HypErc721Upgradeable of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable.upgrade(new_class_hash); + } + } + + impl TokenRouterHooksImpl of TokenRouterHooksTrait { + fn transfer_from_sender_hook( + ref self: TokenRouterComponent::ComponentState, amount_or_id: u256 + ) -> Bytes { + let contract_state = TokenRouterComponent::HasComponent::get_contract(@self); + let token_owner = contract_state.erc721.owner_of(amount_or_id); + assert!(token_owner == get_caller_address(), "Caller is not owner of token"); + + let mut contract_state = TokenRouterComponent::HasComponent::get_contract_mut(ref self); + contract_state.erc721.burn(amount_or_id); + + BytesTrait::new_empty() + } + + fn transfer_to_hook( + ref self: TokenRouterComponent::ComponentState, + recipient: u256, + amount_or_id: u256, + metadata: Bytes + ) { + let recipient_felt: felt252 = recipient.try_into().expect('u256 to felt failed'); + let recipient: ContractAddress = recipient_felt.try_into().unwrap(); + + let mut contract_state = TokenRouterComponent::HasComponent::get_contract_mut(ref self); + + let metadata_byteArray = bytes_to_byte_array(metadata); + contract_state.erc721_uri_storage._set_token_uri(amount_or_id, metadata_byteArray); + contract_state.erc721.mint(recipient, amount_or_id); + } + } + + // free function + fn bytes_to_byte_array(self: Bytes) -> ByteArray { + let mut res: ByteArray = Default::default(); + let mut offset = 0; + while offset < self + .size() { + if offset + 31 <= self.size() { + let (new_offset, value) = self.read_bytes31(offset); + res.append_word(value.into(), 31); + offset = new_offset; + } else { + let (new_offset, value) = self.read_u8(offset); + res.append_byte(value); + offset = new_offset; + } + }; + res + } +} diff --git a/cairo/src/contracts/mocks/test_erc721.cairo b/cairo/src/contracts/mocks/test_erc721.cairo index aba64a3..2e2483d 100644 --- a/cairo/src/contracts/mocks/test_erc721.cairo +++ b/cairo/src/contracts/mocks/test_erc721.cairo @@ -20,6 +20,7 @@ pub trait ITestERC721 { fn is_approved_for_all( self: @TState, owner: ContractAddress, operator: ContractAddress ) -> bool; + fn token_uri(self: @TState, token_id: u256) -> ByteArray; } #[starknet::contract] @@ -33,6 +34,7 @@ mod TestERC721 { component!(path: SRC5Component, storage: src5, event: SRC5Event); impl ERC721Impl = ERC721Component::ERC721Impl; + impl ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl; impl ERC721InternalImpl = ERC721Component::InternalImpl; #[storage] @@ -107,5 +109,9 @@ mod TestERC721 { ) -> bool { self.erc721.is_approved_for_all(owner, operator) } + + fn token_uri(self: @ContractState, token_id: u256) -> ByteArray { + self.erc721.token_uri(token_id) + } } } diff --git a/cairo/src/contracts/token/components/erc721_uri_storage.cairo b/cairo/src/contracts/token/components/erc721_uri_storage.cairo index 0d9af81..42accce 100644 --- a/cairo/src/contracts/token/components/erc721_uri_storage.cairo +++ b/cairo/src/contracts/token/components/erc721_uri_storage.cairo @@ -1,11 +1,14 @@ #[starknet::interface] pub trait IERC721URIStorage { + fn name(self: @TContractState) -> ByteArray; + fn symbol(self: @TContractState) -> ByteArray; fn token_uri(self: @TContractState, token_id: u256) -> ByteArray; } #[starknet::component] pub mod ERC721URIStorageComponent { use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::interface::IERC721Metadata; use openzeppelin::token::erc721::{ ERC721Component, ERC721Component::InternalTrait as ERC721InternalTrait, ERC721Component::ERC721HooksTrait, ERC721Component::ERC721MetadataImpl @@ -18,7 +21,7 @@ pub mod ERC721URIStorageComponent { #[event] #[derive(Drop, starknet::Event)] - enum Event { + pub enum Event { MetadataUpdate: MetadataUpdate, } @@ -28,7 +31,7 @@ pub mod ERC721URIStorageComponent { } #[embeddable_as(ERC721URIStorageImpl)] - impl ERC721URIStorage< + pub impl ERC721URIStorage< TContractState, +HasComponent, +Drop, @@ -36,6 +39,18 @@ pub mod ERC721URIStorageComponent { +ERC721HooksTrait, impl ERC721: ERC721Component::HasComponent, > of super::IERC721URIStorage> { + // returns the NFT name + fn name(self: @ComponentState) -> ByteArray { + let erc721_component = get_dep_component!(self, ERC721); + erc721_component.name() + } + + // returns the NFT symbol + fn symbol(self: @ComponentState) -> ByteArray { + let erc721_component = get_dep_component!(self, ERC721); + erc721_component.symbol() + } + /// Returns the URI associated with a given `token_id`. /// /// This function retrieves the URI for an ERC721 token based on its `token_id`. @@ -71,7 +86,7 @@ pub mod ERC721URIStorageComponent { } #[generate_trait] - impl ERC721URIStorageInternalImpl< + pub impl ERC721URIStorageInternalImpl< TContractState, +HasComponent, +Drop, diff --git a/cairo/src/contracts/token/components/hyp_erc721_component.cairo b/cairo/src/contracts/token/components/hyp_erc721_component.cairo index 14416ab..1a6b6ab 100644 --- a/cairo/src/contracts/token/components/hyp_erc721_component.cairo +++ b/cairo/src/contracts/token/components/hyp_erc721_component.cairo @@ -1,8 +1,6 @@ -use starknet::{ContractAddress, ClassHash}; - #[starknet::interface] pub trait IHypErc721 { - fn initialize(ref self: TState, mint_amount: u256, name: ByteArray, symbol: ByteArray,); + fn initialize(ref self: TState, mint_amount: u256, name: ByteArray, symbol: ByteArray); } #[starknet::component] @@ -22,23 +20,21 @@ pub mod HypErc721Component { ERC721Component::InternalTrait as ERC721InternalTrait, ERC721Component::ERC721HooksTrait, }; - use starknet::{ContractAddress, ClassHash}; - #[storage] struct Storage {} - #[embeddable_as(HypErc721Impl)] - impl HypErc721< + #[generate_trait] + pub impl HypErc721InternalImpl< TContractState, +HasComponent, +Drop, +OwnableComponent::HasComponent, +SRC5Component::HasComponent, - +ERC721Component::ERC721HooksTrait, + +ERC721HooksTrait, impl Mailboxclient: MailboxclientComponent::HasComponent, impl ERC721: ERC721Component::HasComponent, - > of super::IHypErc721> { + > of HypErc721InternalTrait { /// Initializes the ERC721 token contract with a specified mint amount, name, and symbol. /// /// This function sets the name and symbol for the ERC721 token contract and mints the specified number @@ -68,53 +64,4 @@ pub mod HypErc721Component { }; } } - - #[generate_trait] - impl HypErc721InternalImpl< - TContractState, - +HasComponent, - +Drop, - +SRC5Component::HasComponent, - +ERC721Component::ERC721HooksTrait, - impl ERC721: ERC721Component::HasComponent, - > of InternalTrait { - /// Burns a token owned by the sender. - /// - /// This function ensures that the sender is the owner of the specified token before burning it. - /// The token is permanently removed from the sender's balance. - /// - /// # Arguments - /// - /// * `token_id` - A `u256` representing the ID of the token to be burned. - /// - /// # Panics - /// - /// Panics if the caller is not the owner of the token. - fn transfer_from_sender(ref self: ComponentState, token_id: u256) { - let erc721_comp_read = get_dep_component!(@self, ERC721); - assert!( - erc721_comp_read.owner_of(token_id) == starknet::get_caller_address(), - "Caller is not owner of token" - ); - - let mut erc721_comp_write = get_dep_component_mut!(ref self, ERC721); - erc721_comp_write.burn(token_id); - } - - /// Mints a token to a specified recipient. - /// - /// This function mints the specified token to the given recipient's address. The newly minted token - /// will be transferred to the recipient. - /// - /// # Arguments - /// - /// * `recipient` - A `ContractAddress` representing the recipient's address. - /// * `token_id` - A `u256` representing the ID of the token to be minted. - fn transfer_to( - ref self: ComponentState, recipient: ContractAddress, token_id: u256 - ) { - let mut erc721_comp_write = get_dep_component_mut!(ref self, ERC721); - erc721_comp_write.mint(recipient, token_id); - } - } } diff --git a/cairo/src/contracts/token/components/token_router.cairo b/cairo/src/contracts/token/components/token_router.cairo index fca0a0c..5801eb3 100644 --- a/cairo/src/contracts/token/components/token_router.cairo +++ b/cairo/src/contracts/token/components/token_router.cairo @@ -132,7 +132,7 @@ pub mod TokenRouterComponent { } #[embeddable_as(TokenRouterImpl)] - impl TokenRouter< + pub impl TokenRouter< TContractState, +HasComponent, +Drop, diff --git a/cairo/src/contracts/token/extensions/hyp_erc721_URI_collateral.cairo b/cairo/src/contracts/token/extensions/hyp_erc721_URI_collateral.cairo index 3633c6f..3ade625 100644 --- a/cairo/src/contracts/token/extensions/hyp_erc721_URI_collateral.cairo +++ b/cairo/src/contracts/token/extensions/hyp_erc721_URI_collateral.cairo @@ -103,10 +103,11 @@ pub mod HypERC721URICollateral { ref self: ContractState, erc721: ContractAddress, mailbox: ContractAddress, + hook: ContractAddress, owner: ContractAddress ) { self.ownable.initializer(owner); - self.mailboxclient.initialize(mailbox, Option::None, Option::None); + self.mailboxclient.initialize(mailbox, Option::Some(hook), Option::None); self .hyp_erc721_collateral @@ -132,7 +133,13 @@ pub mod HypERC721URICollateral { ref self: TokenRouterComponent::ComponentState, amount_or_id: u256 ) -> Bytes { let mut contract_state = TokenRouterComponent::HasComponent::get_contract_mut(ref self); - contract_state.token_router.transfer_from_sender_hook(amount_or_id); + contract_state + .hyp_erc721_collateral + .wrapped_token + .read() + .transfer_from( + starknet::get_caller_address(), starknet::get_contract_address(), amount_or_id + ); let uri = contract_state .hyp_erc721_collateral diff --git a/cairo/src/contracts/token/extensions/hyp_erc721_URI_storage.cairo b/cairo/src/contracts/token/extensions/hyp_erc721_URI_storage.cairo index db5e0c5..15ce856 100644 --- a/cairo/src/contracts/token/extensions/hyp_erc721_URI_storage.cairo +++ b/cairo/src/contracts/token/extensions/hyp_erc721_URI_storage.cairo @@ -1,48 +1,203 @@ -#[starknet::interface] -pub trait IHypERC721URIStorage { - fn initialize(ref self: TState); - fn balance_of(self: @TState, account: u256) -> u256; - fn token_uri(self: @TState, token_id: u256) -> u256; - fn supports_interface(self: @TState, interface_id: u256) -> bool; -} - #[starknet::contract] pub mod HypERC721URIStorage { + use alexandria_bytes::{Bytes, BytesTrait}; + use hyperlane_starknet::contracts::client::gas_router_component::GasRouterComponent; + use hyperlane_starknet::contracts::client::mailboxclient_component::MailboxclientComponent; + use hyperlane_starknet::contracts::client::router_component::RouterComponent; + use hyperlane_starknet::contracts::token::components::erc721_uri_storage::ERC721URIStorageComponent; + use hyperlane_starknet::contracts::token::components::hyp_erc721_component::{ + HypErc721Component + }; + use hyperlane_starknet::contracts::token::components::token_router::{ + TokenRouterComponent, TokenRouterComponent::TokenRouterHooksTrait, + TokenRouterComponent::MessageRecipientInternalHookImpl, + TokenRouterTransferRemoteHookDefaultImpl + }; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl,}; + use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + use starknet::{ContractAddress, get_caller_address}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: MailboxclientComponent, storage: mailboxclient, event: MailboxclientEvent); + component!(path: RouterComponent, storage: router, event: RouterEvent); + component!(path: GasRouterComponent, storage: gas_router, event: GasRouterEvent); + component!(path: TokenRouterComponent, storage: token_router, event: TokenRouterEvent); + component!(path: HypErc721Component, storage: hyp_erc721, event: HypErc721Event); + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!( + path: ERC721URIStorageComponent, storage: erc721_uri_storage, event: ERC721UriStorageEvent + ); + + // Ownable + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // MailboxClient + #[abi(embed_v0)] + impl MailboxClientImpl = + MailboxclientComponent::MailboxClientImpl; + impl MailboxClientInternalImpl = + MailboxclientComponent::MailboxClientInternalImpl; + + //Router + #[abi(embed_v0)] + impl RouterImpl = RouterComponent::RouterImpl; + impl RouterInternalImpl = RouterComponent::RouterComponentInternalImpl; + + // GasRouter + #[abi(embed_v0)] + impl GasRouterImpl = GasRouterComponent::GasRouterImpl; + + // TokenRouter + #[abi(embed_v0)] + impl TokenRouterImpl = TokenRouterComponent::TokenRouterImpl; + impl TokenRouterInternalImpl = TokenRouterComponent::TokenRouterInternalImpl; + + //HypERC721 + impl HypErc721InternalImpl = HypErc721Component::HypErc721InternalImpl; + + //ERC721 + #[abi(embed_v0)] + impl ERC721URIStorageImpl = + ERC721URIStorageComponent::ERC721URIStorageImpl; + #[abi(embed_v0)] + impl ERC721Impl = ERC721Component::ERC721Impl; + #[abi(embed_v0)] + impl ERC721CamelOnlyImpl = ERC721Component::ERC721CamelOnlyImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + impl ERC721URIStorageInternalImpl = + ERC721URIStorageComponent::ERC721URIStorageInternalImpl; + + //upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + #[storage] struct Storage { - mailbox: u256, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + mailboxclient: MailboxclientComponent::Storage, + #[substorage(v0)] + router: RouterComponent::Storage, + #[substorage(v0)] + gas_router: GasRouterComponent::Storage, + #[substorage(v0)] + token_router: TokenRouterComponent::Storage, + #[substorage(v0)] + hyp_erc721: HypErc721Component::Storage, + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + erc721_uri_storage: ERC721URIStorageComponent::Storage, } - fn constructor() {} + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + MailboxclientEvent: MailboxclientComponent::Event, + #[flat] + RouterEvent: RouterComponent::Event, + #[flat] + GasRouterEvent: GasRouterComponent::Event, + #[flat] + TokenRouterEvent: TokenRouterComponent::Event, + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + HypErc721Event: HypErc721Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + ERC721UriStorageEvent: ERC721URIStorageComponent::Event, + } - impl HypERC721URIStorageImpl of super::IHypERC721URIStorage { - fn initialize(ref self: ContractState) {} + #[constructor] + fn constructor( + ref self: ContractState, + mailbox: ContractAddress, + _mint_amount: u256, + _name: ByteArray, + _symbol: ByteArray, + _hook: ContractAddress, + _interchainSecurityModule: ContractAddress, + owner: ContractAddress, + ) { + self.ownable.initializer(owner); + self + .mailboxclient + .initialize(mailbox, Option::Some(_hook), Option::Some(_interchainSecurityModule)); + self.hyp_erc721.initialize(_mint_amount, _name, _symbol); + } - fn balance_of(self: @ContractState, account: u256) -> u256 { - 0 + #[abi(embed_v0)] + impl HypErc721Upgradeable of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable.upgrade(new_class_hash); } + } - fn token_uri(self: @ContractState, token_id: u256) -> u256 { - 0 - } + impl TokenRouterHooksImpl of TokenRouterHooksTrait { + fn transfer_from_sender_hook( + ref self: TokenRouterComponent::ComponentState, amount_or_id: u256 + ) -> Bytes { + let contract_state = TokenRouterComponent::HasComponent::get_contract(@self); + let token_owner = contract_state.erc721.owner_of(amount_or_id); + assert!(token_owner == get_caller_address(), "Caller is not owner of token"); - fn supports_interface(self: @ContractState, interface_id: u256) -> bool { - false - } - } + let mut contract_state = TokenRouterComponent::HasComponent::get_contract_mut(ref self); + contract_state.erc721.burn(amount_or_id); - #[generate_trait] - impl InternalImpl of InternalTrait { - fn transfer_from_sender(ref self: ContractState, token_id: u256) -> u256 { - 0 + BytesTrait::new_empty() } - fn transfer_to(ref self: ContractState, recipient: u256, token_id: u256, token_uri: u256) {} + fn transfer_to_hook( + ref self: TokenRouterComponent::ComponentState, + recipient: u256, + amount_or_id: u256, + metadata: Bytes + ) { + let recipient_felt: felt252 = recipient.try_into().expect('u256 to felt failed'); + let recipient: ContractAddress = recipient_felt.try_into().unwrap(); + + let mut contract_state = TokenRouterComponent::HasComponent::get_contract_mut(ref self); - fn before_token_transfer( - ref self: ContractState, from: u256, to: u256, token_id: u256, batch_size: u256 - ) {} + let metadata_byteArray = bytes_to_byte_array(metadata); + contract_state.erc721_uri_storage._set_token_uri(amount_or_id, metadata_byteArray); + contract_state.erc721.mint(recipient, amount_or_id); + } + } - fn burn(ref self: ContractState, token_id: u256) {} + // free function + fn bytes_to_byte_array(self: Bytes) -> ByteArray { + let mut res: ByteArray = Default::default(); + let mut offset = 0; + while offset < self + .size() { + if offset + 31 <= self.size() { + let (new_offset, value) = self.read_bytes31(offset); + res.append_word(value.into(), 31); + offset = new_offset; + } else { + let (new_offset, value) = self.read_u8(offset); + res.append_byte(value); + offset = new_offset; + } + }; + res } } diff --git a/cairo/src/contracts/token/hyp_erc20_collateral.cairo b/cairo/src/contracts/token/hyp_erc20_collateral.cairo index 29506c7..e2527b6 100644 --- a/cairo/src/contracts/token/hyp_erc20_collateral.cairo +++ b/cairo/src/contracts/token/hyp_erc20_collateral.cairo @@ -6,7 +6,8 @@ pub mod HypErc20Collateral { use hyperlane_starknet::contracts::client::router_component::RouterComponent; use hyperlane_starknet::contracts::token::components::{ token_router::{ - TokenRouterComponent, TokenRouterComponent::MessageRecipientInternalHookImpl + TokenRouterComponent, TokenRouterComponent::MessageRecipientInternalHookImpl, + TokenRouterTransferRemoteHookDefaultImpl }, hyp_erc20_collateral_component::{ HypErc20CollateralComponent, HypErc20CollateralComponent::TokenRouterHooksImpl @@ -51,6 +52,9 @@ pub mod HypErc20Collateral { HypErc20CollateralComponent::HypErc20CollateralInternalImpl; // Upgradeable impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + // TokenRouter + #[abi(embed_v0)] + impl TokenRouterImpl = TokenRouterComponent::TokenRouterImpl; #[storage] struct Storage { diff --git a/cairo/src/contracts/token/hyp_erc721.cairo b/cairo/src/contracts/token/hyp_erc721.cairo index 8e41baa..1581e52 100644 --- a/cairo/src/contracts/token/hyp_erc721.cairo +++ b/cairo/src/contracts/token/hyp_erc721.cairo @@ -56,8 +56,7 @@ pub mod HypErc721 { impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; // HypERC721 - #[abi(embed_v0)] - impl HypErc721Impl = HypErc721Component::HypErc721Impl; + impl HypErc721InternalImpl = HypErc721Component::HypErc721InternalImpl; // TokenRouter #[abi(embed_v0)] diff --git a/cairo/src/lib.cairo b/cairo/src/lib.cairo index 8de9c45..3079cf2 100644 --- a/cairo/src/lib.cairo +++ b/cairo/src/lib.cairo @@ -37,6 +37,7 @@ mod contracts { pub mod message_recipient; pub mod mock_account; pub mod mock_eth; + pub mod mock_hyp_erc721_uri_storage; pub mod mock_mailbox; pub mod mock_validator_announce; pub mod test_erc20; diff --git a/cairo/src/tests/token/hyp_erc20/common.cairo b/cairo/src/tests/token/hyp_erc20/common.cairo index 9db0b1c..dd7ae46 100644 --- a/cairo/src/tests/token/hyp_erc20/common.cairo +++ b/cairo/src/tests/token/hyp_erc20/common.cairo @@ -72,6 +72,12 @@ pub fn SYMBOL() -> ByteArray { #[starknet::interface] pub trait IHypERC20Test { + // Collateral + fn transfer_from_sender_hook(ref self: TContractState, amount_or_id: u256) -> Bytes; + fn transfer_to_hook( + ref self: TContractState, recipient: ContractAddress, amount: u256, metadata: Bytes + ) -> bool; + fn get_wrapped_token(self: @TContractState) -> ContractAddress; // MailboxClient fn set_hook(ref self: TContractState, _hook: ContractAddress); fn set_interchain_security_module(ref self: TContractState, _module: ContractAddress); @@ -133,18 +139,15 @@ pub struct Setup { pub fn setup() -> Setup { let contract = declare("TestISM").unwrap(); let (default_ism, _) = contract.deploy(@array![]).unwrap(); - println!("DEFAULT_ISM: {:?}", default_ism); let contract = declare("TestPostDispatchHook").unwrap(); let (noop_hook, _) = contract.deploy(@array![]).unwrap(); - println!("NOOP_HOOK: {:?}", noop_hook); let noop_hook = ITestPostDispatchHookDispatcher { contract_address: noop_hook }; let contract = declare("Ether").unwrap(); let mut calldata: Array = array![]; starknet::get_contract_address().serialize(ref calldata); let (eth_address, _) = contract.deploy(@calldata).unwrap(); - println!("ETH: {:?}", eth_address); let eth = MockEthDispatcher { contract_address: eth_address }; eth.mint(ALICE(), 10 * E18); @@ -159,7 +162,6 @@ pub fn setup() -> Setup { ] ) .unwrap(); - println!("LOCAL_MAILBOX: {:?}", local_mailbox); let local_mailbox = IMockMailboxDispatcher { contract_address: local_mailbox }; let (remote_mailbox, _) = contract @@ -172,7 +174,6 @@ pub fn setup() -> Setup { ] ) .unwrap(); - println!("REMOTE_MAILBOX: {:?}", remote_mailbox); let remote_mailbox = IMockMailboxDispatcher { contract_address: remote_mailbox }; local_mailbox.add_remote_mail_box(DESTINATION, remote_mailbox.contract_address); @@ -188,12 +189,10 @@ pub fn setup() -> Setup { TOTAL_SUPPLY.serialize(ref calldata); DECIMALS.serialize(ref calldata); let (primary_token, _) = contract.deploy(@calldata).unwrap(); - println!("PRIMARY_TOKEN: {:?}", primary_token); let primary_token = ITestERC20Dispatcher { contract_address: primary_token }; let (erc20_token, _) = contract.deploy(@calldata).unwrap(); let erc20_token = ITestERC20Dispatcher { contract_address: erc20_token }; - println!("ERC20_TOKEN: {:?}", erc20_token.contract_address); let hyp_erc20_contract = declare("HypErc20").unwrap(); let mut calldata: Array = array![]; @@ -206,12 +205,10 @@ pub fn setup() -> Setup { default_ism.serialize(ref calldata); OWNER().serialize(ref calldata); let (implementation, _) = hyp_erc20_contract.deploy(@calldata).unwrap(); - println!("IMPLEMENTATION: {:?}", implementation); let implementation = IHypERC20TestDispatcher { contract_address: implementation }; let contract = declare("TestInterchainGasPayment").unwrap(); let (igp, _) = contract.deploy(@array![]).unwrap(); - println!("IGP: {:?}", igp); let igp = ITestInterchainGasPaymentDispatcher { contract_address: igp }; let mut calldata: Array = array![]; @@ -224,11 +221,9 @@ pub fn setup() -> Setup { igp.contract_address.serialize(ref calldata); starknet::get_contract_address().serialize(ref calldata); let (remote_token, _) = hyp_erc20_contract.deploy(@calldata).unwrap(); - println!("REMOTE_TOKEN: {:?}", remote_token); let remote_token = IHypERC20TestDispatcher { contract_address: remote_token }; let (local_token, _) = hyp_erc20_contract.deploy(@calldata).unwrap(); - println!("LOCAL_TOKEN: {:?}", local_token); let local_token = IHypERC20TestDispatcher { contract_address: local_token }; let local_token_address: felt252 = local_token.contract_address.into(); diff --git a/cairo/src/tests/token/hyp_erc20/hyp_erc20_collateral_test.cairo b/cairo/src/tests/token/hyp_erc20/hyp_erc20_collateral_test.cairo index 8b13789..3cc020a 100644 --- a/cairo/src/tests/token/hyp_erc20/hyp_erc20_collateral_test.cairo +++ b/cairo/src/tests/token/hyp_erc20/hyp_erc20_collateral_test.cairo @@ -1 +1,156 @@ +use alexandria_bytes::{Bytes, BytesTrait}; +use hyperlane_starknet::contracts::client::gas_router_component::{ + GasRouterComponent::GasRouterConfig, IGasRouterDispatcher, IGasRouterDispatcherTrait +}; +use hyperlane_starknet::contracts::mocks::{ + test_post_dispatch_hook::{ + ITestPostDispatchHookDispatcher, ITestPostDispatchHookDispatcherTrait + }, + mock_mailbox::{IMockMailboxDispatcher, IMockMailboxDispatcherTrait}, + test_erc20::{ITestERC20Dispatcher, ITestERC20DispatcherTrait}, + test_interchain_gas_payment::{ + ITestInterchainGasPaymentDispatcher, ITestInterchainGasPaymentDispatcherTrait + }, + mock_eth::{MockEthDispatcher, MockEthDispatcherTrait} +}; +use hyperlane_starknet::contracts::token::hyp_erc20_collateral::HypErc20Collateral; +use hyperlane_starknet::tests::setup::{ + OWNER, LOCAL_DOMAIN, DESTINATION_DOMAIN, RECIPIENT_ADDRESS, MAILBOX, DESTINATION_MAILBOX, + setup_protocol_fee, setup_mock_hook, PROTOCOL_FEE, INITIAL_SUPPLY, setup_mock_fee_hook, + setup_mock_ism, setup_mock_token +}; +use hyperlane_starknet::tests::token::hyp_erc20::common::{ + setup, Setup, TOTAL_SUPPLY, DECIMALS, ORIGIN, DESTINATION, TRANSFER_AMT, ALICE, BOB, + perform_remote_transfer_with_emit, perform_remote_transfer_and_gas, E18, + IHypERC20TestDispatcher, IHypERC20TestDispatcherTrait, enroll_remote_router, + enroll_local_router, set_custom_gas_config, REQUIRED_VALUE, GAS_LIMIT +}; +use hyperlane_starknet::utils::utils::U256TryIntoContractAddress; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use snforge_std::{ + start_prank, stop_prank, declare, ContractClassTrait, CheatTarget, spy_events, SpyOn +}; +use starknet::ContractAddress; +fn setup_hyp_erc20_collateral() -> (IHypERC20TestDispatcher, Setup) { + let setup = setup(); + let hyp_erc20_collateral_contract = declare("HypErc20Collateral").unwrap(); + let constructor_args: Array = array![ + setup.local_mailbox.contract_address.into(), + setup.primary_token.contract_address.into(), + ALICE().into(), + setup.noop_hook.contract_address.into(), + setup.primary_token.contract_address.into() // just a placeholder + ]; + + let (collateral_address, _) = hyp_erc20_collateral_contract.deploy(@constructor_args).unwrap(); + let collateral = IHypERC20TestDispatcher { contract_address: collateral_address }; + + // Enroll remote router + let remote_token_address: felt252 = setup.remote_token.contract_address.into(); + start_prank(CheatTarget::One(collateral.contract_address), ALICE()); + collateral.enroll_remote_router(DESTINATION, remote_token_address.into()); + stop_prank(CheatTarget::One(collateral.contract_address)); + + // Transfer tokens to collateral contract and ALICE + setup.primary_token.transfer(collateral.contract_address, 1000 * E18); + setup.primary_token.transfer(ALICE(), 1000 * E18); + let addr: felt252 = collateral.contract_address.into(); + // Enroll remote router for the remote token + setup.remote_token.enroll_remote_router(ORIGIN, addr.into()); + (collateral, setup) +} + +fn perform_remote_transfer_collateral( + setup: @Setup, + collateral: @IHypERC20TestDispatcher, + msg_value: u256, + extra_gas: u256, + amount: u256, + approve: bool +) { + // Approve + if approve { + start_prank(CheatTarget::One(*setup.primary_token.contract_address), ALICE()); + (*setup.primary_token).approve(*collateral.contract_address, amount); + stop_prank(CheatTarget::One(*setup.primary_token.contract_address)); + } + // Remote transfer + start_prank(CheatTarget::One(*collateral.contract_address), ALICE()); + let bob_felt: felt252 = BOB().into(); + let bob_address: u256 = bob_felt.into(); + (*collateral) + .transfer_remote(DESTINATION, bob_address, amount, msg_value, Option::None, Option::None); + + process_transfers_collateral(setup, collateral, BOB(), amount); + + let remote_token = IERC20Dispatcher { + contract_address: (*setup).remote_token.contract_address + }; + assert_eq!(remote_token.balance_of(BOB()), amount); + + stop_prank(CheatTarget::One(*collateral.contract_address)); +} + +fn process_transfers_collateral( + setup: @Setup, collateral: @IHypERC20TestDispatcher, recipient: ContractAddress, amount: u256 +) { + start_prank( + CheatTarget::One((*setup).remote_token.contract_address), + (*setup).remote_mailbox.contract_address + ); + let local_token_address: felt252 = (*collateral).contract_address.into(); + let mut message = BytesTrait::new_empty(); + message.append_address(recipient); + message.append_u256(amount); + (*setup).remote_token.handle(ORIGIN, local_token_address.into(), message); + stop_prank(CheatTarget::One((*setup).remote_token.contract_address)); +} + +#[test] +fn test_remote_transfer() { + let (collateral, setup) = setup_hyp_erc20_collateral(); + let balance_before = collateral.balance_of(ALICE()); + start_prank(CheatTarget::One(collateral.contract_address), ALICE()); + perform_remote_transfer_collateral(@setup, @collateral, REQUIRED_VALUE, 0, TRANSFER_AMT, true); + stop_prank(CheatTarget::One(collateral.contract_address)); + // Check balance after transfer + assert_eq!( + collateral.balance_of(ALICE()), + balance_before - TRANSFER_AMT, + "Incorrect balance after transfer" + ); +} + +#[test] +#[should_panic] +fn test_remote_transfer_invalid_allowance() { + let (collateral, setup) = setup_hyp_erc20_collateral(); + start_prank(CheatTarget::One(collateral.contract_address), ALICE()); + perform_remote_transfer_collateral(@setup, @collateral, REQUIRED_VALUE, 0, TRANSFER_AMT, false); + stop_prank(CheatTarget::One(collateral.contract_address)); +} + +#[test] +fn test_remote_transfer_with_custom_gas_config() { + let (collateral, setup) = setup_hyp_erc20_collateral(); + // Check balance before transfer + let balance_before = collateral.balance_of(ALICE()); + start_prank(CheatTarget::One(collateral.contract_address), ALICE()); + // Set custom gas config + collateral.set_hook(setup.igp.contract_address); + let config = array![GasRouterConfig { domain: DESTINATION, gas: GAS_LIMIT }]; + collateral.set_destination_gas(Option::Some(config), Option::None, Option::None); + // Do a remote transfer + perform_remote_transfer_collateral( + @setup, @collateral, REQUIRED_VALUE, setup.igp.gas_price(), TRANSFER_AMT, true + ); + + stop_prank(CheatTarget::One(collateral.contract_address)); + // Check balance after transfer + assert_eq!( + collateral.balance_of(ALICE()), + balance_before - TRANSFER_AMT, + "Incorrect balance after transfer" + ); +} diff --git a/cairo/src/tests/token/hyp_erc20/hyp_erc20_lockbox_test.cairo b/cairo/src/tests/token/hyp_erc20/hyp_erc20_lockbox_test.cairo index 557941a..f4df591 100644 --- a/cairo/src/tests/token/hyp_erc20/hyp_erc20_lockbox_test.cairo +++ b/cairo/src/tests/token/hyp_erc20/hyp_erc20_lockbox_test.cairo @@ -79,7 +79,6 @@ fn setup_lockbox() -> (Setup, IHypERC20LockboxTestDispatcher) { let contract = declare("XERC20Test").unwrap(); let (xerc20, _) = contract.deploy(@calldata).unwrap(); let xerc20 = IXERC20TestDispatcher { contract_address: xerc20 }; - println!("XERC20: {:?}", xerc20.contract_address); let contract = declare("XERC20LockboxTest").unwrap(); @@ -89,8 +88,6 @@ fn setup_lockbox() -> (Setup, IHypERC20LockboxTestDispatcher) { let (lockbox, _) = contract.deploy(@calldata).unwrap(); let lockbox = IXERC20LockboxTestDispatcher { contract_address: lockbox }; - println!("LOCKBOX: {:?}", lockbox.contract_address); - let contract = declare("HypXERC20Lockbox").unwrap(); let mut calldata: Array = array![]; @@ -102,7 +99,6 @@ fn setup_lockbox() -> (Setup, IHypERC20LockboxTestDispatcher) { let (xerc20lockbox, _) = contract.deploy(@calldata).unwrap(); let xerc20lockbox = IHypERC20LockboxTestDispatcher { contract_address: xerc20lockbox }; - println!("XERC20LOCKBOX: {:?}", xerc20lockbox.contract_address); let remote_token_address: felt252 = setup.remote_token.contract_address.into(); xerc20lockbox.enroll_remote_router(DESTINATION, remote_token_address.into()); diff --git a/cairo/src/tests/token/hyp_erc20/hyp_xerc20_test.cairo b/cairo/src/tests/token/hyp_erc20/hyp_xerc20_test.cairo index fde5568..dfa112d 100644 --- a/cairo/src/tests/token/hyp_erc20/hyp_xerc20_test.cairo +++ b/cairo/src/tests/token/hyp_erc20/hyp_xerc20_test.cairo @@ -23,7 +23,6 @@ fn setup_xerc20() -> Setup { DECIMALS.serialize(ref calldata); let (xerc20, _) = contract.deploy(@calldata).unwrap(); setup.primary_token = ITestERC20Dispatcher { contract_address: xerc20 }; - println!("XERC20Test: {:?}", xerc20); let contract = declare("HypXERC20").unwrap(); let (local_token, _) = contract @@ -38,7 +37,6 @@ fn setup_xerc20() -> Setup { ) .unwrap(); setup.local_token = IHypERC20TestDispatcher { contract_address: local_token }; - println!("HypXERC20: {:?}", local_token); setup .local_token diff --git a/cairo/src/tests/token/hyp_erc721/common.cairo b/cairo/src/tests/token/hyp_erc721/common.cairo index 596f2f1..9a32092 100644 --- a/cairo/src/tests/token/hyp_erc721/common.cairo +++ b/cairo/src/tests/token/hyp_erc721/common.cairo @@ -45,10 +45,10 @@ pub fn ZERO_ADDRESS() -> ContractAddress { fn EMPTY_STRING() -> ByteArray { "" } -fn NAME() -> ByteArray { +pub fn NAME() -> ByteArray { "Hyperlane Hedgehogs" } -fn SYMBOL() -> ByteArray { +pub fn SYMBOL() -> ByteArray { "HHH" } pub fn ALICE() -> ContractAddress { @@ -63,8 +63,8 @@ fn PROXY_ADMIN() -> ContractAddress { pub const INITIAL_SUPPLY: u256 = 10; pub const ORIGIN: u32 = 11; pub const DESTINATION: u32 = 22; -const TRANSFER_ID: u256 = 0; -fn URI() -> ByteArray { +pub const TRANSFER_ID: u256 = 0; +pub fn URI() -> ByteArray { "http://bit.ly/3reJLpx" } @@ -125,6 +125,11 @@ pub trait IHypErc721Test { fn get_wrapped_token(self: @TContractState) -> ContractAddress; // HypERC721 fn initialize(ref self: TContractState, mint_amount: u256, name: ByteArray, symbol: ByteArray); + // HypERC721URIStorage + fn name(self: @TContractState) -> ByteArray; + fn symbol(self: @TContractState) -> ByteArray; + fn token_uri(self: @TContractState, token_id: u256) -> ByteArray; + fn set_token_uri(ref self: TContractState, token_id: u256, uri: ByteArray); } #[derive(Copy, Drop)] @@ -148,27 +153,22 @@ pub fn setup() -> Setup { let mut calldata: Array = array![]; (INITIAL_SUPPLY * 2).serialize(ref calldata); let (primary_token, _) = contract.deploy(@calldata).unwrap(); - println!("PRIMARY TOKEN: {:?}", primary_token); let local_primary_token = ITestERC721Dispatcher { contract_address: primary_token }; let (remote_primary_token, _) = contract.deploy(@calldata).unwrap(); - println!("REMOTE PRIMARY TOKEN: {:?}", remote_primary_token); let remote_primary_token = ITestERC721Dispatcher { contract_address: remote_primary_token }; let contract = declare("TestPostDispatchHook").unwrap(); let (noop_hook, _) = contract.deploy(@array![]).unwrap(); - println!("NOOP HOOK: {:?}", noop_hook); let noop_hook = ITestPostDispatchHookDispatcher { contract_address: noop_hook }; let contract = declare("TestISM").unwrap(); let (default_ism, _) = contract.deploy(@array![]).unwrap(); - println!("DEFAULT ISM: {:?}", default_ism); let contract = declare("Ether").unwrap(); let mut calldata: Array = array![]; starknet::get_contract_address().serialize(ref calldata); let (eth_address, _) = contract.deploy(@calldata).unwrap(); - println!("ETH: {:?}", eth_address); //let eth = MockEthDispatcher { contract_address: eth_address }; let contract = declare("MockMailbox").unwrap(); @@ -182,7 +182,6 @@ pub fn setup() -> Setup { ] ) .unwrap(); - println!("LOCAL MAILBOX: {:?}", local_mailbox); let local_mailbox = IMockMailboxDispatcher { contract_address: local_mailbox }; let (remote_mailbox, _) = contract @@ -195,7 +194,6 @@ pub fn setup() -> Setup { ] ) .unwrap(); - println!("REMOTE MAILBOX: {:?}", remote_mailbox); let remote_mailbox = IMockMailboxDispatcher { contract_address: remote_mailbox }; local_mailbox.set_default_hook(noop_hook.contract_address); @@ -213,7 +211,6 @@ pub fn setup() -> Setup { ] ) .unwrap(); - println!("REMOTE TOKEN: {:?}", remote_token); let remote_token = IHypErc721TestDispatcher { contract_address: remote_token }; let hyp_erc721_contract = declare("HypErc721").unwrap(); @@ -226,14 +223,11 @@ pub fn setup() -> Setup { calldata.append(default_ism.into()); calldata.append(starknet::get_contract_address().into()); let (local_token, _) = hyp_erc721_contract.deploy(@calldata).unwrap(); - println!("LOCAL TOKEN: {:?}", local_token); let local_token = IHypErc721TestDispatcher { contract_address: local_token }; let contract = declare("MockAccount").unwrap(); let (alice, _) = contract.deploy(@array![PUB_KEY]).unwrap(); - println!("ALICE: {:?}", alice); let (bob, _) = contract.deploy(@array![PUB_KEY]).unwrap(); - println!("BOB: {:?}", bob); Setup { local_primary_token, @@ -260,7 +254,6 @@ pub fn deploy_remote_token(mut setup: Setup, is_collateral: bool) -> Setup { ZERO_ADDRESS().serialize(ref calldata); starknet::get_contract_address().serialize(ref calldata); let (remote_token, _) = setup.hyp_erc721_collateral_contract.deploy(@calldata).unwrap(); - println!("NEW REMOTE TOKEN: {:?}", remote_token); setup.remote_token = IHypErc721TestDispatcher { contract_address: remote_token }; setup .remote_primary_token @@ -277,7 +270,6 @@ pub fn deploy_remote_token(mut setup: Setup, is_collateral: bool) -> Setup { ZERO_ADDRESS().serialize(ref calldata); starknet::get_contract_address().serialize(ref calldata); let (remote_token, _) = setup.hyp_erc721_contract.deploy(@calldata).unwrap(); - println!("NEW REMOTE TOKEN: {:?}", remote_token); setup.remote_token = IHypErc721TestDispatcher { contract_address: remote_token }; } let local_token_address: felt252 = setup.local_token.contract_address.into(); @@ -286,7 +278,6 @@ pub fn deploy_remote_token(mut setup: Setup, is_collateral: bool) -> Setup { } pub fn process_transfer(setup: @Setup, recipient: ContractAddress, token_id: u256) { - println!("INSIDE PROCESS TRANSFER"); start_prank( CheatTarget::One((*setup).remote_token.contract_address), (*setup).remote_mailbox.contract_address @@ -299,7 +290,6 @@ pub fn process_transfer(setup: @Setup, recipient: ContractAddress, token_id: u25 } pub fn perform_remote_transfer(setup: @Setup, msg_value: u256, token_id: u256) { - println!("INSIDE PERFORM REMOTE TRANSFER"); let alice_address: felt252 = (*setup).alice.into(); (*setup) .local_token diff --git a/cairo/src/tests/token/hyp_erc721/hyp_erc721_collateral_uri_storage_test.cairo b/cairo/src/tests/token/hyp_erc721/hyp_erc721_collateral_uri_storage_test.cairo index 8b13789..8c61342 100644 --- a/cairo/src/tests/token/hyp_erc721/hyp_erc721_collateral_uri_storage_test.cairo +++ b/cairo/src/tests/token/hyp_erc721/hyp_erc721_collateral_uri_storage_test.cairo @@ -1 +1,71 @@ +use alexandria_bytes::Bytes; +use hyperlane_starknet::contracts::client::router_component::{ + IRouterDispatcher, IRouterDispatcherTrait +}; +use hyperlane_starknet::contracts::mocks::test_erc721::{ + ITestERC721Dispatcher, ITestERC721DispatcherTrait +}; +use hyperlane_starknet::contracts::token::components::token_router::{ + ITokenRouterDispatcher, ITokenRouterDispatcherTrait +}; +use snforge_std::cheatcodes::contract_class::{ContractClass, ContractClassTrait}; +use snforge_std::{ + declare, CheatTarget, EventSpy, EventAssertions, spy_events, SpyOn, start_prank, stop_prank, + EventFetcher, event_name_hash +}; +use starknet::ContractAddress; +use super::common::{ + setup, DESTINATION, INITIAL_SUPPLY, Setup, IHypErc721TestDispatcher, + IHypErc721TestDispatcherTrait, ALICE, BOB, deploy_remote_token, perform_remote_transfer, + ZERO_ADDRESS, NAME, SYMBOL, URI, TRANSFER_ID, process_transfer +}; + +fn setup_erc721_collateral_uri_storage() -> Setup { + let mut setup = setup(); + + let contract = declare("HypERC721URICollateral").unwrap(); + let mut calldata: Array = array![]; + setup.local_primary_token.contract_address.serialize(ref calldata); + setup.local_mailbox.contract_address.serialize(ref calldata); + setup.noop_hook.contract_address.serialize(ref calldata); + starknet::get_contract_address().serialize(ref calldata); + let (hyp_erc721_uri_collateral, _) = contract.deploy(@calldata).unwrap(); + let hyp_erc721_uri_collateral = IHypErc721TestDispatcher { + contract_address: hyp_erc721_uri_collateral + }; + + let remote_token_address: felt252 = setup.remote_token.contract_address.into(); + hyp_erc721_uri_collateral.enroll_remote_router(DESTINATION, remote_token_address.into()); + + setup + .local_primary_token + .transfer_from( + starknet::get_contract_address(), + hyp_erc721_uri_collateral.contract_address, + INITIAL_SUPPLY + 1 + ); + + setup.local_token = hyp_erc721_uri_collateral; + + setup +} + +#[test] +fn test_erc721_collateral_uri_storage_remote_transfer_revert_burned() { + let setup = setup_erc721_collateral_uri_storage(); + + let setup = deploy_remote_token(setup, false); + setup.local_primary_token.approve(setup.local_token.contract_address, 0); + let bob_address: felt252 = setup.bob.into(); + setup + .local_token + .transfer_remote( + DESTINATION, bob_address.into(), TRANSFER_ID, 2500, Option::None, Option::None + ); + process_transfer(@setup, setup.bob, 0); + assert_eq!(setup.remote_token.balance_of(setup.bob), 1); + assert_eq!( + setup.local_token.balance_of(starknet::get_contract_address()), INITIAL_SUPPLY * 2 - 2 + ); +} diff --git a/cairo/src/tests/token/hyp_erc721/hyp_erc721_uri_storage_test.cairo b/cairo/src/tests/token/hyp_erc721/hyp_erc721_uri_storage_test.cairo index 8b13789..dfb12ca 100644 --- a/cairo/src/tests/token/hyp_erc721/hyp_erc721_uri_storage_test.cairo +++ b/cairo/src/tests/token/hyp_erc721/hyp_erc721_uri_storage_test.cairo @@ -1 +1,63 @@ +use alexandria_bytes::Bytes; +use hyperlane_starknet::contracts::client::router_component::{ + IRouterDispatcher, IRouterDispatcherTrait +}; +use hyperlane_starknet::contracts::mocks::test_erc721::{ + ITestERC721Dispatcher, ITestERC721DispatcherTrait +}; +use hyperlane_starknet::contracts::token::components::token_router::{ + ITokenRouterDispatcher, ITokenRouterDispatcherTrait +}; +use snforge_std::cheatcodes::contract_class::{ContractClass, ContractClassTrait}; +use snforge_std::{ + declare, CheatTarget, EventSpy, EventAssertions, spy_events, SpyOn, start_prank, stop_prank, + EventFetcher, event_name_hash +}; +use starknet::ContractAddress; +use super::common::{ + setup, DESTINATION, INITIAL_SUPPLY, Setup, IHypErc721TestDispatcher, + IHypErc721TestDispatcherTrait, ALICE, BOB, deploy_remote_token, perform_remote_transfer, + ZERO_ADDRESS, NAME, SYMBOL, URI +}; + +fn setup_erc721_uri_storage() -> Setup { + let mut setup = setup(); + + let contract = declare("MockHypERC721URIStorage").unwrap(); + let mut calldata: Array = array![]; + setup.local_mailbox.contract_address.serialize(ref calldata); + INITIAL_SUPPLY.serialize(ref calldata); + NAME().serialize(ref calldata); + SYMBOL().serialize(ref calldata); + setup.noop_hook.contract_address.serialize(ref calldata); + setup.default_ism.serialize(ref calldata); + starknet::get_contract_address().serialize(ref calldata); + let (hyp_erc721_uri_storage, _) = contract.deploy(@calldata).unwrap(); + let hyp_erc721_uri_storage = IHypErc721TestDispatcher { + contract_address: hyp_erc721_uri_storage + }; + + hyp_erc721_uri_storage.set_token_uri(0, URI()); + let remote_token_address: felt252 = setup.remote_token.contract_address.into(); + hyp_erc721_uri_storage.enroll_remote_router(DESTINATION, remote_token_address.into()); + + setup.local_token = hyp_erc721_uri_storage; + + setup +} + +#[test] +#[should_panic] +fn test_erc721_uri_storage_remote_transfer_revert_burned() { + let setup = setup_erc721_uri_storage(); + + let setup = deploy_remote_token(setup, false); + perform_remote_transfer(@setup, 2500, 0); + + let balance = setup.local_token.balance_of(starknet::get_contract_address()); + assert_eq!(balance, INITIAL_SUPPLY - 1); + + let uri = setup.local_token.token_uri(0); + assert_eq!(uri, URI()); +} diff --git a/cairo/src/tests/token/vault_extensions/hyp_erc20_collateral_vault_deposit_test.cairo b/cairo/src/tests/token/vault_extensions/hyp_erc20_collateral_vault_deposit_test.cairo index b954cfb..5321e6b 100644 --- a/cairo/src/tests/token/vault_extensions/hyp_erc20_collateral_vault_deposit_test.cairo +++ b/cairo/src/tests/token/vault_extensions/hyp_erc20_collateral_vault_deposit_test.cairo @@ -66,7 +66,6 @@ fn setup_vault() -> (Setup, IERC4626Dispatcher, IHypERC20CollateralVaultDepositD name.serialize(ref calldata); symbol.serialize(ref calldata); let (vault, _) = contract.deploy(@calldata).unwrap(); - println!("VAULT: {:?}", vault); let contract = declare("HypERC20CollateralVaultDeposit").unwrap(); let mut calldata: Array = array![]; @@ -76,7 +75,6 @@ fn setup_vault() -> (Setup, IERC4626Dispatcher, IHypERC20CollateralVaultDepositD setup.noop_hook.contract_address.serialize(ref calldata); setup.implementation.interchain_security_module().serialize(ref calldata); let (implementation, _) = contract.deploy(@calldata).unwrap(); - println!("HypERC20CollateralVaultDeposit: {:?}", implementation); setup.local_token = IHypERC20TestDispatcher { contract_address: implementation }; setup .local_token diff --git a/rust/build.rs b/rust/build.rs index 167ca37..29849e6 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -1,12 +1,23 @@ -use std::{collections::HashMap, env::current_dir, path::PathBuf}; - +use std::{collections::HashMap, env::current_dir, path::{PathBuf, Path}, fs}; use ethers::prelude::Abigen; +fn check_path_exists(path: &Path) { + if !path.exists() { + panic!("Path does not exist: {:?}", path); + } +} + fn generate_eth_bind(name: &str, abi_file: &str, bind_out: PathBuf) { + // Check if the ABI file exists + let abi_file_path = Path::new(abi_file); + check_path_exists(abi_file_path); + + // Remove output file if it exists if bind_out.exists() { - std::fs::remove_file(&bind_out).unwrap(); + fs::remove_file(&bind_out).unwrap(); } + // Generate Ethereum bindings Abigen::new(name, abi_file) .unwrap() .generate() @@ -16,8 +27,13 @@ fn generate_eth_bind(name: &str, abi_file: &str, bind_out: PathBuf) { } fn generate_strk_bind(name: &str, abi_file: &str, bind_out: PathBuf) { + // Check if the ABI file exists + let abi_file_path = Path::new(abi_file); + check_path_exists(abi_file_path); + + // Remove output file if it exists if bind_out.exists() { - std::fs::remove_file(&bind_out).unwrap(); + fs::remove_file(&bind_out).unwrap(); } let mut aliases = HashMap::new(); @@ -50,6 +66,11 @@ fn main() { .unwrap() .join("tests") .join("contracts/eth/bind"); + + // Check if the Ethereum ABI directory exists + check_path_exists(ð_abi_base); + check_path_exists(ð_bind_base); + let eth_deployments = [ ("Mailbox", "mailbox"), ("FastHypERC20", "fast_hyp_erc20"), @@ -73,13 +94,20 @@ fn main() { // Generate Starknet bindings let strk_abi_base = current_dir() .unwrap() - .parent() + .parent() // Move one directory up to source directory .unwrap() - .join("contracts/target/dev"); + .join("cairo") + .join("target") + .join("dev"); let strk_bind_base = current_dir() .unwrap() .join("tests") .join("contracts/strk/bind"); + + // Check if the Starknet ABI directory exists + check_path_exists(&strk_abi_base); + check_path_exists(&strk_bind_base); + let strk_deployments = [ ("mailbox", "mailbox"), ("domain_routing_ism", "routing"), diff --git a/rust/tests/contracts/strk/utils.rs b/rust/tests/contracts/strk/utils.rs index 3341d79..51ce303 100644 --- a/rust/tests/contracts/strk/utils.rs +++ b/rust/tests/contracts/strk/utils.rs @@ -16,7 +16,7 @@ use starknet::{ use super::{types::Codes, StarknetAccount}; -const BUILD_PATH_PREFIX: &str = "../contracts/target/dev/hyperlane_starknet_"; +const BUILD_PATH_PREFIX: &str = "../cairo/target/dev/hyperlane_starknet_"; const KATANA_RPC_URL: &str = "http://localhost:5050"; diff --git a/scripts/.env.example b/scripts/.env.example new file mode 100644 index 0000000..4dfd31d --- /dev/null +++ b/scripts/.env.example @@ -0,0 +1,5 @@ +STARKNET_RPC_URL= +ACCOUNT_ADDRESS= +BENEFICIARY_ADDRESS= +NETWORK= +PRIVATE_KEY= \ No newline at end of file diff --git a/scripts/contract_config.json b/scripts/contract_config.json new file mode 100644 index 0000000..030311a --- /dev/null +++ b/scripts/contract_config.json @@ -0,0 +1,216 @@ +{ + "contracts": { + "merkleroot_multisig_ism": { + "name": "merkleroot_multisig_ism", + "constructor": { + "owner": { + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + }, + "validators": { + "type": "Span", + "value": [ + "0x0000000000000000000000000000000000000000000000000000000000000002" + ] + }, + "threshold": { + "type": "u32", + "value": "1" + } + } + }, + "protocol_fee": { + "name": "protocol_fee", + "constructor": { + "max_protocol_fee_low": { + "type": "u128", + "value": "1000000000000000000" + }, + "max_protocol_fee_high": { + "type": "u128", + "value": "0" + }, + "protocol_fee_low": { + "type": "u128", + "value": "10000000000000000" + }, + "protocol_fee_high": { + "type": "u128", + "value": "0" + }, + "beneficiary": { + "type": "ContractAddress", + "value": "$BENEFICIARY_ADDRESS" + }, + "owner": { + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + }, + "token_address": { + "type": "ContractAddress", + "value": "0x049D36570D4e46f48e99674bd3fcc84644DdD6b96F7C741B1562B82f9e004dC7" + } + } + }, + "merkle_tree_hook" : { + "name": "merkle_tree_hook", + "constructor": { + "mailbox": { + "type": "ContractAddress", + "value": "$mailbox" + }, + "owner": { + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + } + } + }, + "noop_ism": { + "name": "noop_ism", + "constructor": { + + } + }, + "hook": { + "name": "hook", + "constructor": { + + } + }, + "pausable_ism": { + "name": "pausable_ism", + "constructor": { + "owner": { + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + } + } + }, + "trusted_relayer_ism": { + "name": "trusted_relayer_ism", + "constructor": { + "mailbox":{ + "type": "ContractAddress", + "value": "$mailbox" + }, + "trusted_relayer": { + "type": "ContractAddress", + "value": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + } + }, + "mailbox": { + "name": "mailbox", + "constructor": { + "local_domain": { + "type": "u32", + "value": "100" + }, + "owner": { + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + }, + "default_ism": { + "type": "ContractAddress", + "value": "$merkleroot_multisig_ism" + }, + "default_hook":{ + "type": "ContractAddress", + "value": "$hook" + }, + "required_hook": { + "type": "ContractAddress", + "value": "$protocol_fee" + } + } + }, + "validator_announce": { + "name": "validator_announce", + "constructor": { + "mailbox": { + "type": "ContractAddress", + "value": "$mailbox" + }, + "owner": { + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + } + } + }, + "aggregation": { + "name": "aggregation", + "constructor": { + "owner":{ + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + }, + "modules": { + "type": "Span", + "value": [ + "0x0000000000000000000000000000000000000000000000000000000000000002" + ] + }, + "threshold": { + "type": "u32", + "value": "1" + } + } + }, + "messageid_multisig_ism": { + "name": "messageid_multisig_ism", + "constructor": { + "owner": { + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + }, + "validators": { + "type": "Span", + "value": [ + "0x0000000000000000000000000000000000000000000000000000000000000003" + ] + }, + "threshold": { + "type": "u32", + "value": "1" + } + } + }, + "default_fallback_routing_ism": { + "name": "default_fallback_routing_ism", + "constructor": { + "owner": { + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + }, + "mailbox": { + "type": "ContractAddress", + "value": "$mailbox" + } + } + }, + "domain_routing_ism": { + "name": "domain_routing_ism", + "constructor": { + "owner": { + "type": "ContractAddress", + "value": "$OWNER_ADDRESS" + } + } + } + }, + "deploymentOrder": [ + "merkleroot_multisig_ism", + "messageid_multisig_ism", + "domain_routing_ism", + "noop_ism", + "pausable_ism", + "aggregation", + "protocol_fee", + "hook", + "mailbox", + "merkle_tree_hook", + "default_fallback_routing_ism", + "trusted_relayer_ism", + "validator_announce" + ] + } \ No newline at end of file diff --git a/scripts/deploy.ts b/scripts/deploy.ts new file mode 100644 index 0000000..dc77543 --- /dev/null +++ b/scripts/deploy.ts @@ -0,0 +1,144 @@ +import { + Account, + Contract, + json, + Provider, + CallData, + RpcProvider, + ContractFactory, + ContractFactoryParams +} from "starknet"; +import fs from "fs"; +import path from "path"; +import dotenv from "dotenv"; + +dotenv.config(); + +const BUILD_PATH = "../cairo/target/dev/hyperlane_starknet"; +const ACCOUNT_ADDRESS = process.env.ACCOUNT_ADDRESS; +const PRIVATE_KEY = process.env.PRIVATE_KEY; +const CONFIG_FILE = "contract_config.json"; +const NETWORK = process.env.NETWORK +const DEPLOYED_CONTRACTS_FILE = path.join('deployments', `${NETWORK}_deployed_contracts.json`); + + +interface DeployedContracts { + [key: string]: string; +} + +interface ContractConfig { + name: string; + constructor: Record; +} + +interface Config { + contracts: Record; + deploymentOrder: string[]; +} + +async function buildAccount(): Promise { + const provider = new RpcProvider({ nodeUrl: process.env.STARKNET_RPC_URL }); + + if (!PRIVATE_KEY || !ACCOUNT_ADDRESS) { + throw new Error("Private key or account address not set in .env file"); + } + if (!NETWORK) { + throw new Error('NETWORK environment variable is not set'); + } + + return new Account(provider, ACCOUNT_ADDRESS, PRIVATE_KEY); +} + +function getCompiledContract(name: string): any { + const contractPath = `${BUILD_PATH}_${name}.contract_class.json`; + return json.parse(fs.readFileSync(contractPath).toString("ascii")); +} + +function getCompiledContractCasm(name: string): any { + const contractPath = `${BUILD_PATH}_${name}.compiled_contract_class.json`; + return json.parse(fs.readFileSync(contractPath).toString("ascii")); +} + +function processConstructorArgs(args: Record, deployedContracts: DeployedContracts): any { + return Object.entries(args).reduce((acc, [key, { type, value }]) => { + if (typeof value === 'string' && value.startsWith('$')) { + if (value === '$OWNER_ADDRESS') { + acc[key] = ACCOUNT_ADDRESS; + } else if (value === '$BENEFICIARY_ADDRESS') { + acc[key] = process.env.BENEFICIARY_ADDRESS; + } else { + const contractName = value.slice(1); + if (deployedContracts[contractName]) { + acc[key] = deployedContracts[contractName]; + } else { + throw new Error(`Contract ${contractName} not yet deployed, required for ${key}`); + } + } + } else { + acc[key] = value; + } + return acc; + }, {} as any); +} + +async function deployContract( + account: Account, + contractName: string, + constructorArgs: ContractConfig['constructor'], + deployedContracts: DeployedContracts +): Promise { + console.log(`Deploying contract ${contractName}...`); + + const compiledContract = getCompiledContract(contractName); + const casm = getCompiledContractCasm(contractName); + const processedArgs = processConstructorArgs(constructorArgs, deployedContracts); + const constructorCalldata = CallData.compile(processedArgs); + const params: ContractFactoryParams = { + compiledContract, + account, + casm + }; + + const contractFactory = new ContractFactory(params); const contract = await contractFactory.deploy(constructorCalldata); + + console.log(`Contract ${contractName} deployed at address:`, contract.address); + + return contract.address; +} + +async function deployContracts(): Promise { + try { + const account = await buildAccount(); + const config: Config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')); + const deployedContracts: DeployedContracts = {}; + + for (const contractName of config.deploymentOrder) { + const address = await deployContract( + account, + contractName, + config.contracts[contractName].constructor, + deployedContracts + ); + deployedContracts[contractName] = address; + } + + console.log("All contracts deployed successfully:"); + console.log(deployedContracts); + + fs.writeFileSync(DEPLOYED_CONTRACTS_FILE, JSON.stringify(deployedContracts, null, 2)); + console.log(`Deployed contracts saved to ${DEPLOYED_CONTRACTS_FILE}`); + + return deployedContracts; + } catch (error) { + console.error("Deployment failed:", error); + throw error; + } +} + +deployContracts() + .then((addresses) => { + console.log("Deployment successful. Contract addresses:", addresses); + }) + .catch((error) => { + console.error("Deployment failed:", error); + }); \ No newline at end of file diff --git a/scripts/package-lock.json b/scripts/package-lock.json new file mode 100644 index 0000000..c96c7fb --- /dev/null +++ b/scripts/package-lock.json @@ -0,0 +1,540 @@ +{ + "name": "scripts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scripts", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/node": "^22.0.2", + "dotenv": "^16.4.5", + "fs": "^0.0.1-security", + "starknet": "^6.11.0" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", + "integrity": "sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/starknet": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@scure/starknet/-/starknet-1.0.0.tgz", + "integrity": "sha512-o5J57zY0f+2IL/mq8+AYJJ4Xpc1fOtDhr+mFQKbHnYFmm3WQrC+8zj2HEgxak1a+x86mhmBC1Kq305KUpVf0wg==", + "dependencies": { + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/starknet/node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/starknet/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@starknet-io/types-js": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.7.7.tgz", + "integrity": "sha512-WLrpK7LIaIb8Ymxu6KF/6JkGW1sso988DweWu7p5QY/3y7waBIiPvzh27D9bX5KIJNRDyOoOVoHVEKYUYWZ/RQ==" + }, + "node_modules/@types/node": { + "version": "22.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.2.tgz", + "integrity": "sha512-yPL6DyFwY5PiMVEwymNeqUTKsDczQBJ/5T7W/46RwLU/VH+AA8aT5TZkvBviLKLbbm0hlfftEkGrNzfRk/fofQ==", + "dependencies": { + "undici-types": "~6.11.1" + } + }, + "node_modules/abi-wan-kanabi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/abi-wan-kanabi/-/abi-wan-kanabi-2.2.2.tgz", + "integrity": "sha512-sTCv2HyNIj1x2WFUoc9oL8ZT9liosrL+GoqEGZJK1kDND096CfA7lwx06vLxLWMocQ41FQXO3oliwoh/UZHYdQ==", + "dependencies": { + "ansicolors": "^0.3.2", + "cardinal": "^2.1.1", + "fs-extra": "^10.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "generate": "dist/generate.js" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" + }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fetch-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-3.0.1.tgz", + "integrity": "sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==", + "dependencies": { + "set-cookie-parser": "^2.4.8", + "tough-cookie": "^4.0.0" + } + }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-starknet-core": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/get-starknet-core/-/get-starknet-core-4.0.0.tgz", + "integrity": "sha512-6pLmidQZkC3wZsrHY99grQHoGpuuXqkbSP65F8ov1/JsEI8DDLkhsAuLCKFzNOK56cJp+f1bWWfTJ57e9r5eqQ==", + "dependencies": { + "@starknet-io/types-js": "^0.7.7" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lossless-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.0.1.tgz", + "integrity": "sha512-l0L+ppmgPDnb+JGxNLndPtJZGNf6+ZmVaQzoxQm3u6TXmhdnsA+YtdVR8DjzZd/em58686CQhOFDPewfJ4l7MA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dependencies": { + "esprima": "~4.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + }, + "node_modules/starknet": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/starknet/-/starknet-6.11.0.tgz", + "integrity": "sha512-u50KrGDi9fbu1Ogu7ynwF/tSeFlp3mzOg1/Y5x50tYFICImo3OfY4lOz9OtYDk404HK4eUujKkhov9tG7GAKlg==", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "^1.4.0", + "@scure/base": "~1.1.3", + "@scure/starknet": "~1.0.0", + "abi-wan-kanabi": "^2.2.2", + "fetch-cookie": "^3.0.0", + "get-starknet-core": "^4.0.0-next.3", + "isomorphic-fetch": "^3.0.0", + "lossless-json": "^4.0.1", + "pako": "^2.0.4", + "starknet-types-07": "npm:@starknet-io/types-js@^0.7.7", + "ts-mixer": "^6.0.3", + "url-join": "^4.0.1" + } + }, + "node_modules/starknet-types-07": { + "name": "@starknet-io/types-js", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.7.7.tgz", + "integrity": "sha512-WLrpK7LIaIb8Ymxu6KF/6JkGW1sso988DweWu7p5QY/3y7waBIiPvzh27D9bX5KIJNRDyOoOVoHVEKYUYWZ/RQ==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" + }, + "node_modules/undici-types": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 0000000..8a5a087 --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,18 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@types/node": "^22.0.2", + "dotenv": "^16.4.5", + "fs": "^0.0.1-security", + "starknet": "^6.11.0" + } +}