From 4d85d53de7ffc78ce5d52b15454209b2bcadd3a8 Mon Sep 17 00:00:00 2001 From: urimihura <43704209+uri-99@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:29:14 -0300 Subject: [PATCH 1/3] chore: add CI/CD basics for Cairo and Solidity contracts (#122) * feat: start GH Actions * test: empty commit to see if CI is executed * wip: make test in CI * wip: run sn tests in ci * feat: add make test * fix: add snfoundry * fix: snfoundry version * feat: eth test on CI * style: Update .github/workflows/run-tests-ETH.yml Co-authored-by: Roberto J Catalan * style: Update .github/workflows/run-tests-SN.yml Co-authored-by: Roberto J Catalan * style: CI yml files names changed * build(eth-test): run eth-test action on pushes to main * build(eth-test): add new line at eof * test: remove escrow_cancel tests * test fix: use is_paused instead of paused_state --------- Co-authored-by: Uriel Mihura Co-authored-by: Roberto J Catalan Co-authored-by: JuArce --- .github/workflows/eth-test.yml | 20 +++ .github/workflows/sn-test.yml | 37 +++++ contracts/cairo/src/escrow.cairo | 2 +- contracts/cairo/src/lib.cairo | 1 - .../cairo/src/tests/test_escrow_cancel.cairo | 145 ------------------ .../cairo/src/tests/test_escrow_pause.cairo | 51 ++++-- 6 files changed, 96 insertions(+), 160 deletions(-) create mode 100644 .github/workflows/eth-test.yml create mode 100644 .github/workflows/sn-test.yml delete mode 100644 contracts/cairo/src/tests/test_escrow_cancel.cairo diff --git a/.github/workflows/eth-test.yml b/.github/workflows/eth-test.yml new file mode 100644 index 00000000..fbce4b7b --- /dev/null +++ b/.github/workflows/eth-test.yml @@ -0,0 +1,20 @@ +name: ETH YABTransfer Tests + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + +jobs: + test-ETH: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run tests + run: make ethereum-test diff --git a/.github/workflows/sn-test.yml b/.github/workflows/sn-test.yml new file mode 100644 index 00000000..2d153ba1 --- /dev/null +++ b/.github/workflows/sn-test.yml @@ -0,0 +1,37 @@ +name: SN Escrow Tests + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + +jobs: + test-SN: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install scarb + uses: software-mansion/setup-scarb@v1 + with: + scarb-version: "2.3.1" + + - name: Install starkliup + run: | + curl https://get.starkli.sh | sh + + - name: Install Starkli + run: | + /home/runner/.config/.starkli/bin/starkliup --version 0.1.20 + sudo mv /home/runner/.config/.starkli/bin/starkli /usr/local/bin/ + + - name: Install snFoundry + uses: foundry-rs/setup-snfoundry@v2 + with: + starknet-foundry-version: 0.12.0 + + - name: Run make starknet-test + run: | + make starknet-test diff --git a/contracts/cairo/src/escrow.cairo b/contracts/cairo/src/escrow.cairo index 04a8619f..651f2542 100644 --- a/contracts/cairo/src/escrow.cairo +++ b/contracts/cairo/src/escrow.cairo @@ -36,7 +36,7 @@ mod Escrow { use openzeppelin::{ access::ownable::OwnableComponent, upgrades::{UpgradeableComponent, interface::IUpgradeable}, - security::PausableComponent + security::PausableComponent, }; use starknet::{ ContractAddress, EthAddress, ClassHash, get_caller_address, get_contract_address, diff --git a/contracts/cairo/src/lib.cairo b/contracts/cairo/src/lib.cairo index a7188ff1..87cce74f 100644 --- a/contracts/cairo/src/lib.cairo +++ b/contracts/cairo/src/lib.cairo @@ -16,7 +16,6 @@ mod mocks { #[cfg(test)] mod tests { mod test_escrow_allowance; - mod test_escrow_cancel; mod test_escrow_pause; mod test_escrow_upgrade; mod test_escrow_ownable; diff --git a/contracts/cairo/src/tests/test_escrow_cancel.cairo b/contracts/cairo/src/tests/test_escrow_cancel.cairo deleted file mode 100644 index 532260bd..00000000 --- a/contracts/cairo/src/tests/test_escrow_cancel.cairo +++ /dev/null @@ -1,145 +0,0 @@ -mod Escrow { - use core::to_byte_array::FormatAsByteArray; - use core::serde::Serde; - use core::traits::Into; - use starknet::{EthAddress, ContractAddress}; - use integer::BoundedInt; - - use snforge_std::{declare, ContractClassTrait, L1Handler, L1HandlerTrait}; - use snforge_std::{CheatTarget, start_prank, stop_prank, start_warp, stop_warp}; - - use yab::mocks::mock_Escrow_changed_functions::{IEscrow_mock_changed_functionsDispatcher, IEscrow_mock_changed_functionsDispatcherTrait}; - use yab::mocks::mock_pausableEscrow::{IEscrow_mockPausableDispatcher, IEscrow_mockPausableDispatcherTrait}; - use yab::interfaces::IERC20::{IERC20Dispatcher, IERC20DispatcherTrait}; - use yab::escrow::{IEscrowDispatcher, IEscrowDispatcherTrait, Order}; - use yab::interfaces::IEVMFactsRegistry::{ - IEVMFactsRegistryDispatcher, IEVMFactsRegistryDispatcherTrait - }; - - use yab::tests::utils::{ - constants::EscrowConstants::{ - USER, OWNER, MM_STARKNET, MM_ETHEREUM, ETH_TRANSFER_CONTRACT, ETH_USER - }, - }; - - use openzeppelin::{ - upgrades::{ - UpgradeableComponent, - interface::{IUpgradeable, IUpgradeableDispatcher, IUpgradeableDispatcherTrait} - }, - }; - - fn setup() -> (IEscrowDispatcher, IERC20Dispatcher) { - setup_general(BoundedInt::max(), BoundedInt::max()) - } - - fn setup_approved(approved: u256) -> (IEscrowDispatcher, IERC20Dispatcher){ - setup_general(BoundedInt::max(), approved) - } - - fn setup_balance(balance: u256) -> (IEscrowDispatcher, IERC20Dispatcher){ - setup_general(balance, BoundedInt::max()) - } - - fn setup_general(balance: u256, approved: u256) -> (IEscrowDispatcher, IERC20Dispatcher){ - let eth_token = deploy_erc20('ETH', '$ETH', BoundedInt::max(), OWNER()); - let escrow = deploy_escrow( - OWNER(), - ETH_TRANSFER_CONTRACT(), - MM_ETHEREUM(), - MM_STARKNET(), - eth_token.contract_address - ); - - start_prank(CheatTarget::One(eth_token.contract_address), OWNER()); - eth_token.transfer(USER(), balance); - stop_prank(CheatTarget::One(eth_token.contract_address)); - - start_prank(CheatTarget::One(eth_token.contract_address), USER()); - eth_token.approve(escrow.contract_address, approved); - stop_prank(CheatTarget::One(eth_token.contract_address)); - - (escrow, eth_token) - } - - fn deploy_escrow( - escrow_owner: ContractAddress, - eth_transfer_contract: EthAddress, - mm_ethereum_contract: EthAddress, - mm_starknet_contract: ContractAddress, - native_token_eth_starknet: ContractAddress - ) -> IEscrowDispatcher { - let escrow = declare('Escrow'); - let mut calldata: Array = ArrayTrait::new(); - calldata.append(escrow_owner.into()); - calldata.append(eth_transfer_contract.into()); - calldata.append(mm_ethereum_contract.into()); - calldata.append(mm_starknet_contract.into()); - calldata.append(native_token_eth_starknet.into()); - let address = escrow.deploy(@calldata).unwrap(); - return IEscrowDispatcher { contract_address: address }; - } - - fn deploy_erc20( - name: felt252, symbol: felt252, initial_supply: u256, recipent: ContractAddress - ) -> IERC20Dispatcher { - let erc20 = declare('ERC20'); - let mut calldata = array![name, symbol]; - Serde::serialize(@initial_supply, ref calldata); - calldata.append(recipent.into()); - let address = erc20.deploy(@calldata).unwrap(); - return IERC20Dispatcher { contract_address: address }; - } - - #[test] - fn test_cancel_order() { - let (escrow, eth_token) = setup_balance(500); - - start_prank(CheatTarget::One(escrow.contract_address), USER()); - let order = Order { recipient_address: 12345.try_into().unwrap(), amount: 500, fee: 0 }; - let order_id = escrow.set_order(order); - - // check balance - assert(eth_token.balanceOf(escrow.contract_address) == 500, 'set_order: wrong balance '); - assert(eth_token.balanceOf(MM_STARKNET()) == 0, 'set_order: wrong balance'); - assert(eth_token.balanceOf(USER()) == 0, 'set_order: wrong allowance'); - - start_warp(CheatTarget::One(escrow.contract_address), 43201); - escrow.cancel_order(order_id); - stop_warp(CheatTarget::One(escrow.contract_address)); - - stop_prank(CheatTarget::One(escrow.contract_address)); - - // check balance - assert(eth_token.balanceOf(escrow.contract_address) == 0, 'cancel_order: wrong balance '); - assert(eth_token.balanceOf(MM_STARKNET()) == 0, 'cancel_order: wrong balance'); - assert(eth_token.balanceOf(USER()) == 500, 'cancel_order: wrong allowance'); - } - - #[test] - #[should_panic(expected: ('Not enough time has passed',))] - fn test_cancel_order_fail_time() { - let (escrow, eth_token) = setup_balance(500); - - start_prank(CheatTarget::One(escrow.contract_address), USER()); - let order = Order { recipient_address: 12345.try_into().unwrap(), amount: 500, fee: 0 }; - let order_id = escrow.set_order(order); - - escrow.cancel_order(order_id); - stop_prank(CheatTarget::One(escrow.contract_address)); - } - - #[test] - #[should_panic(expected: ('Only sender allowed',))] - fn test_cancel_order_fail_sender() { - let (escrow, eth_token) = setup_balance(500); - - start_prank(CheatTarget::One(escrow.contract_address), USER()); - let order = Order { recipient_address: 12345.try_into().unwrap(), amount: 500, fee: 0 }; - let order_id = escrow.set_order(order); - - start_warp(CheatTarget::One(escrow.contract_address), 43201); - start_prank(CheatTarget::One(escrow.contract_address), MM_STARKNET()); - escrow.cancel_order(order_id); - } -} diff --git a/contracts/cairo/src/tests/test_escrow_pause.cairo b/contracts/cairo/src/tests/test_escrow_pause.cairo index 8e30469f..d42162a1 100644 --- a/contracts/cairo/src/tests/test_escrow_pause.cairo +++ b/contracts/cairo/src/tests/test_escrow_pause.cairo @@ -25,7 +25,11 @@ mod Escrow { use openzeppelin::{ upgrades::{ UpgradeableComponent, - interface::{IUpgradeable, IUpgradeableDispatcher, IUpgradeableDispatcherTrait} + interface::{IUpgradeable, IUpgradeableDispatcher, IUpgradeableDispatcherTrait}, + }, + security::{ + PausableComponent, + interface::{IPausable, IPausableDispatcher, IPausableDispatcherTrait}, }, }; @@ -94,29 +98,35 @@ mod Escrow { #[test] fn test_start_unpaused() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), OWNER()); - assert(escrow.pause_state() == false, 'Should start unpaused'); + assert(pausable.is_paused() == false, 'Should start unpaused'); stop_prank(CheatTarget::One(escrow.contract_address)); } #[test] fn test_pause() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), OWNER()); escrow.pause(); - assert(escrow.pause_state() == true, 'Should be paused'); + assert(pausable.is_paused() == true, 'Should be paused'); stop_prank(CheatTarget::One(escrow.contract_address)); } #[test] fn test_pause_unpause() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), OWNER()); - assert(escrow.pause_state() == false, 'Should start unpaused'); + assert(pausable.is_paused() == false, 'Should start unpaused'); escrow.pause(); - assert(escrow.pause_state() == true, 'Should be paused'); + assert(pausable.is_paused() == true, 'Should be paused'); escrow.unpause(); - assert(escrow.pause_state() == false, 'Should be unpaused'); + assert(pausable.is_paused() == false, 'Should be unpaused'); stop_prank(CheatTarget::One(escrow.contract_address)); } @@ -124,8 +134,10 @@ mod Escrow { #[should_panic(expected: ('Caller is not the owner',))] fn test_fail_pause_not_owner() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), USER()); - assert(escrow.pause_state() == false, 'Should start unpaused'); + assert(pausable.is_paused() == false, 'Should start unpaused'); escrow.pause(); stop_prank(CheatTarget::One(escrow.contract_address)); } @@ -134,10 +146,12 @@ mod Escrow { #[should_panic(expected: ('Caller is not the owner',))] fn test_fail_unpause_not_owner() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), OWNER()); - assert(escrow.pause_state() == false, 'Should start unpaused'); + assert(pausable.is_paused() == false, 'Should start unpaused'); escrow.pause(); - assert(escrow.pause_state() == true, 'Should be paused'); + assert(pausable.is_paused() == true, 'Should be paused'); stop_prank(CheatTarget::One(escrow.contract_address)); start_prank(CheatTarget::One(escrow.contract_address), USER()); @@ -149,10 +163,12 @@ mod Escrow { #[should_panic(expected: ('Pausable: paused',))] fn test_fail_pause_while_paused() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), OWNER()); - assert(escrow.pause_state() == false, 'Should start unpaused'); + assert(pausable.is_paused() == false, 'Should start unpaused'); escrow.pause(); - assert(escrow.pause_state() == true, 'Should be paused'); + assert(pausable.is_paused() == true, 'Should be paused'); escrow.pause(); stop_prank(CheatTarget::One(escrow.contract_address)); } @@ -161,10 +177,12 @@ mod Escrow { #[should_panic(expected: ('Pausable: not paused',))] fn test_fail_unpause_while_unpaused() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), OWNER()); - assert(escrow.pause_state() == false, 'Should start unpaused'); + assert(pausable.is_paused() == false, 'Should start unpaused'); escrow.unpause(); - assert(escrow.pause_state() == false, 'Should be unpaused'); + assert(pausable.is_paused() == false, 'Should be unpaused'); escrow.unpause(); stop_prank(CheatTarget::One(escrow.contract_address)); } @@ -173,6 +191,8 @@ mod Escrow { #[should_panic(expected: ('Pausable: paused',))] fn test_fail_set_order_when_paused() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), OWNER()); escrow.pause(); stop_prank(CheatTarget::One(escrow.contract_address)); @@ -186,6 +206,8 @@ mod Escrow { #[test] fn test_set_order_when_unpaused_after_prev_pause() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), USER()); let order = Order { recipient_address: 12345.try_into().unwrap(), amount: 500, fee: 0 }; @@ -206,6 +228,7 @@ mod Escrow { #[test] fn test_upgrade_when_paused() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; let upgradeable = IUpgradeableDispatcher { contract_address: escrow.contract_address }; start_prank(CheatTarget::One(escrow.contract_address), OWNER()); @@ -221,6 +244,8 @@ mod Escrow { #[test] fn test_fail_call_l1_handler_while_paused() { let (escrow, _) = setup(); + let pausable = IPausableDispatcher { contract_address: escrow.contract_address }; + start_prank(CheatTarget::One(escrow.contract_address), OWNER()); escrow.pause(); stop_prank(CheatTarget::One(escrow.contract_address)); From 866ba14a21fcc49b34cd4dfe791013858a7e5cf3 Mon Sep 17 00:00:00 2001 From: Julian Arce <52429267+JuArce@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:30:35 -0300 Subject: [PATCH 2/3] test: integration test CICD (#137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Upgradeable - Escrow (#35) * Starknet escrow script + make build, clean & test * add ethereum escrow deploy + documentation * rename commands Makefile * update makefile + readme * fix makefile clean commands * OZ dependency + escrow impl * fix import * forge install: openzeppelin-contracts v5.0.1 * fix feedback comments * fix wrong env name (thx @uri-99) * refactor deploy.sh + add upgrade.sh (wip) * refactor deploy.sh & upgrade.sh * add EscrowV2 for test upgradeable * add upgradeable tests * revert method name changes * fix upgrade.sh + refactor * rename SN_PROXY_ESCROW_ADDRESS to SN_ESCROW_ADDRESS * fix deploy error + change upgrade log * eof * update readme * revert solidity files changes * remove "proxy" from echo * rename starknet rpc env * resolve comments * remove openzeppelin-contracts * rename and move EscrowV2 * fix feedback comments * update README * revert .gitmodules change * improve deploy script + docu * improve upgrade test * refactor: create contracts without herodotus (#90) * feat: escrow contract without herodotus * fix: change ethereum withrawer in mm_bot * feat: upgreadable Escrow_herodotus contract * refactor: readme with herodotus * refactor: remove comment * refactor: add tmp solution disclaimer on readme * refactor: rename starknet escrow contract * refactor: update wording README.md Co-authored-by: urimihura <43704209+uri-99@users.noreply.github.com> * refactor: remove transfer.sh * fix: lowercase escrow module --------- Co-authored-by: urimihura <43704209+uri-99@users.noreply.github.com> * fix sed -i bug in mac * remove rpc export * refactor: deployment scripts to avoid errors * fix: add param for contract addr missing * fix: use . instead of source * fix: run starknet-build on starknet-deploy-and-connect * build: install dependencies in test cicd [WIP] * build: allow run test cicd manually * build: set python version to 3.10.12 in test cicd * build: run psql install in sudo mode * build: run katana * build: setup katana account * fix: call correct setup katana account script * fix: run setup_katana_account with sudo * fix: check setup_katana_account permissions * fix: add x permission to setup_katana_account * build: install foundry and run anvil * fix: run source on foundry install * fix: run source on foundry install * fix: run source on foundry install * fix: run source with sudo * fix: use . instead of source * fix: login before run source * build: use foundry actions * fix: remove tests * build: separate test cicd in sections * build: clone starknet-messaging-dev repository * build: deploy starknet messaging contract * build: install forge * build: deploy YABTransfer * build: export variables for YABTransfer deploy * build: install forge for YABTransfer * build: add skip_verify variable to YABTransfer deploy * fix: use correct statement for --verify flag * build: deploy starknet escrow contract TODO: deploy ERC20 for NATIVE_TOKEN_ETH_STARKNET * fix: add --private-key flag to deploy.sh * fix: use private-key instead of keystore * fix: remove invalid space in deploy.sh * fix: use private-key or keystore in declare too * build: run starknet-build fix: export STARKNET_RPC * build: update scarb version * build: deploy ERC20 token * build: add .env.test for cairo contracts * fix: set ESCROW_CONTRACT_ADDRESS to 0x0 in .env.test * fix: do not deploy ERC20 (just for test purposes) * fix: deploy ERC20 * fix: use STARKNET_ACCOUNT_ADDRESS instead of STARKNET_ACCOUNT in ERC20 declare refactor: use if statement for conditional flags * fix: export STARKNET_RPC in .env.test file * fix: export STARKNET_RPC from test script instead of .env.test * build: export STARKNET_RPC for Escrow deploy * build: deploy ERC20 and Escrow in the same step * fix: export ERC20 address as NATIVE_TOKEN_ETH_STARKNET * fix: print correct ERC20 address * build: run transfer on escrow contract * fix: use correct path to transfer.sh * fix(transfer.sh): use correct multiline separator * chore(transfer.sh): print env variables * fix(transfer.sh): do not export ESCROW_CONTRACT_ADDRESS in .env.test * build: export .env.test once fix(transfer.sh): remove unnecessary prints * refactor: rename transfer.sh to set_order.sh * build: run set_order.sh in separate step * build: run set_order in same step with deploys * build: use correct path to set_order.sh * build: set escrow address in Ethereum contract * fix(test.yml) export ETH_RPC_URL * build: set withdraw selector in Ethereum contract * build: run transfer in Ethereum * fix: add parameters to transfer signature * fix: call transfer without payable parameter * fix: uint256 typo in transfer * fix: add --value flag to send transfer * build(test.yml): set anvil gas price to 0 * build(test.yml): run withdraw.sh * build(test.yml): sleep 15 seconds after transfer * build(test.yml): set eth_contract as variable in transfer.sh and withdraw.sh * build(test.yml): use YAB_TRANSFER_PROXY_ADDRESS instead of ETH_CONTRACT_ADDRESS * fix(withdraw.sh): set eth_contract variable correctly * fix(cairo/deploy.sh): fix keystore and private key validation * build(transfer.sh): check ethereum balances from mm and destination addresses * fix(transfer.sh): use destination address hex format to check balances * refactor(test.tml): set amount as env variable * refactor(test.tml): reformat script to improve readability * build(withdraw.sh): check mm starknet wallet balances * build(withdraw.sh): check get_order_used after withdrawal * fix(withdraw.sh): invoke get_used_order with --account flag * fix(withdraw.sh): remove double white space * fix(withdraw.sh): add --private-key flag fix(withdraw.sh): use u256 instead of uint256 * fix(withdraw.sh): use call instead of invoke to get_used_order * build(test.yml): set messaging contract on katana run * build(test.yml): deploy starknet messaging contract in l2 * build(test.yml): use dojo 0.4.4 * build(withdraw.sh): sleep 60 seconds before check withdraw status * build(withdraw.sh): use fee_token as ERC20 in l2 * refactor(test.yml): do not deploy ERC20. Katana already provides one refactor(test.yml): move ATIVE_TOKEN_ETH_STARKNET to .env.test * refactor(withdraw.sh): remove sleep 60 * build(test.yml): add assert script to validate final values * fix(assert.sh): use eval for condition check * fix(withdraw.sh): sleep 15 seconds after withdrawal * fix(assert.sh): remove eval operator * build(test.yml): disable python and psql setup * fix(assert.sh): use eval operator * build(assert.sh): check escrow and mm final balances * build(assert.sh): show l2 balances as integers * build(assert.sh): add new line before asserts * refactor(test.yml): improve steps readability * fix(test.yml): install forge for starknet messaging contract * build(test.yml): use .env.test file for ethereum variables * fix(test.yml): export ETHERSCAN_API_KEY * fix(test.yml): use . instead of source * fix(test.yml): do not use .env.test in solidity. It does not work and I don't know why * chore: delete deploy_erc20.sh script * chore: add new line at eof * fix: typo in error message * refactor: import colors.sh in actions scripts * refactor: rename test.yml to integration-test.yml * test: get destination final balance using cast --------- Co-authored-by: Santiago Galván (Dub) Co-authored-by: Uriel Mihura Co-authored-by: Uri Co-authored-by: Santos Rosati Co-authored-by: urimihura <43704209+uri-99@users.noreply.github.com> Co-authored-by: rcatalan98 --- .github/workflows/integration-test.yml | 178 +++++++++ .github/workflows/scripts/assert.sh | 27 ++ .github/workflows/scripts/set_order.sh | 14 + .../workflows/scripts/setup_katana_account.sh | 17 + .github/workflows/scripts/transfer.sh | 24 ++ .github/workflows/scripts/withdraw.sh | 25 ++ .github/workflows/test.yml | 83 ----- contracts/cairo/.env.test | 9 + contracts/cairo/deploy.sh | 12 +- contracts/cairo/src/escrow.cairo | 6 +- contracts/cairo/src/escrow_herodotus.cairo | 309 ++++++++++++++++ contracts/cairo/src/mocks/mock_EscrowV2.cairo | 337 ++++++++++++++++++ .../src/tests/test_escrow_allowance.cairo | 2 +- contracts/solidity/.env.test | 6 + contracts/solidity/deploy.sh | 2 +- 15 files changed, 959 insertions(+), 92 deletions(-) create mode 100644 .github/workflows/integration-test.yml create mode 100755 .github/workflows/scripts/assert.sh create mode 100755 .github/workflows/scripts/set_order.sh create mode 100755 .github/workflows/scripts/setup_katana_account.sh create mode 100755 .github/workflows/scripts/transfer.sh create mode 100755 .github/workflows/scripts/withdraw.sh delete mode 100644 .github/workflows/test.yml create mode 100644 contracts/cairo/.env.test create mode 100644 contracts/cairo/src/escrow_herodotus.cairo create mode 100644 contracts/cairo/src/mocks/mock_EscrowV2.cairo create mode 100644 contracts/solidity/.env.test diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 00000000..0bae806c --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,178 @@ +name: Integration Test +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + +defaults: + run: + shell: bash + +jobs: + Test: + runs-on: ubuntu-latest + environment: Test + + services: + postgres: + image: postgres:latest + env: + POSTGRES_DB: postgres_db # optional (defaults to `postgres`) + POSTGRES_PASSWORD: postgres_password # required + POSTGRES_PORT: 5432 # optional (defaults to `5432`) + POSTGRES_USER: postgres_user # optional (defaults to `postgres`) + # `POSTGRES_HOST` is `localhost` + ports: + - 5432:5432 # maps tcp port 5432 on service container to the host + options: >- # set health checks to wait until postgres has started + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + # Python setup TODO activate when mm-bot is ready +# - name: Setup Python +# uses: actions/setup-python@v5 +# with: +# python-version: 3.10.12 +# architecture: x64 +# +# - name: Install dependencies +# run: | +# python -m pip install --upgrade pip +# pip install -r mm-bot/requirements.txt + + # Postgres setup TODO activate when mm-bot is ready +# - name: Install PSQL +# run: | +# sudo apt-get update +# sudo apt-get install --yes postgresql-client +# +# - name: Create tables +# run: | +# psql -h localhost -d postgres_db -U postgres_user -f mm-bot/resources/schema.sql +# psql -h localhost -d postgres_db -U postgres_user -c "SELECT * FROM block;" +# env: +# PGPASSWORD: postgres_password + + # Ethereum Setup + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Install Forge + run: | + cd contracts/solidity + forge install + + # Starknet Setup + - name: Install scarb + uses: software-mansion/setup-scarb@v1 + with: + scarb-version: "2.3.1" + + - name: Install starkliup + run: curl https://get.starkli.sh | sh + + - name: Install Starkli + run: | + /home/runner/.config/.starkli/bin/starkliup --version 0.1.20 + sudo mv /home/runner/.config/.starkli/bin/starkli /usr/local/bin/ + + - name: Setup Katana .env + run: | + cp .github/workflows/katana/katana.env .env + + - name: Download Katana + run: | + wget https://github.com/dojoengine/dojo/releases/download/v0.4.4/dojo_v0.4.4_linux_amd64.tar.gz + tar -xzvf dojo_v0.4.4_linux_amd64.tar.gz + rm sozo torii dojo-language-server + + # Run Anvil + - name: Run Anvil + run: | + anvil & + + # Deploy Starknet Messaging Contract in L1 + - name: Clone https://github.com/glihm/starknet-messaging-dev + uses: GuillaumeFalourd/clone-github-repo-action@v2.3 + with: + branch: 'main' + owner: 'glihm' + repository: 'starknet-messaging-dev' + + - name: Install Forge + run: | + cd starknet-messaging-dev/solidity + forge install + + - name: Deploy Starknet Messaging Contract + run: | + cd starknet-messaging-dev/solidity + cp anvil.env .env + source .env + forge script script/LocalTesting.s.sol:LocalSetup --broadcast --rpc-url ${ETH_RPC_URL} + + # Run Katana + - name: Run Katana + run: | + ./katana --messaging starknet-messaging-dev/anvil.messaging.json & + + - name: Setup Katana Account + run: | + .github/workflows/scripts/setup_katana_account.sh + + # Setup Starknet messaging + - name: Setup Starknet messaging + run: | + cd starknet-messaging-dev/cairo + source katana.env + scarb build + starkli declare ./target/dev/messaging_tuto_contract_msg.contract_class.json --keystore-password "" + starkli deploy 0x02d6b666ade3a9ee98430d565830604b90954499c590fa05a9844bdf4d3a574b \ + --salt 0x1234 \ + --keystore-password "" + + # Build Ethereum Contract + - name: Build Ethereum Contract + run: | + make ethereum-build + + # Build Starknet Contract + - name: Build Starknet Contract + run: | + make starknet-build + + # Deploy PaymentRegistry, Escrow, set escrow, set withdraw selector and test complete flow + - name: Deploy and test complete flow + run: | + export ETH_RPC_URL=${{vars.ETH_RPC_URL}} + export ETHERSCAN_API_KEY=${{vars.ETHERSCAN_API_KEY}} + export ETH_PRIVATE_KEY=${{vars.ETH_PRIVATE_KEY}} + export SN_MESSAGING_ADDRESS=${{vars.SN_MESSAGING_ADDRESS}} + export MM_ETHEREUM_WALLET=${{vars.MM_ETHEREUM_WALLET}} + export SKIP_VERIFY=true + . ./contracts/solidity/deploy.sh + + export STARKNET_RPC=${{vars.STARKNET_RPC}} + source ./contracts/cairo/.env.test + . ./contracts/cairo/deploy.sh + + . ./contracts/solidity/set_escrow.sh + . ./contracts/solidity/set_withdraw_selector.sh + + export AMOUNT=1000000000000000000 + . ./.github/workflows/scripts/set_order.sh + + . ./.github/workflows/scripts/transfer.sh + . ./.github/workflows/scripts/withdraw.sh + + . ./.github/workflows/scripts/assert.sh + + # Run mm-bot (it should run a single process order listening to the contract once) diff --git a/.github/workflows/scripts/assert.sh b/.github/workflows/scripts/assert.sh new file mode 100755 index 00000000..3200ccae --- /dev/null +++ b/.github/workflows/scripts/assert.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +. contracts/utils/colors.sh #for ANSI colors + +assert() { + # Usage: assert + if eval "$1"; then + printf "${GREEN}✓ $2 passed.${RESET}\n" + else + printf "${RED}⨯ $2 assertion failed: Expected value: $3, Obtained value: $4.${RESET}\n" + exit 1 + fi +} + +echo "" + +DESTINATION_FINAL_BALANCE=$(cast balance --rpc-url $ETH_RPC_URL $DESTINATION_ADDRESS) +EXPECTED_DESTINATION_FINAL_BALANCE=10001000000000000000000 +assert "[[ $DESTINATION_FINAL_BALANCE -eq $EXPECTED_DESTINATION_FINAL_BALANCE ]]" "Destination balance" "$EXPECTED_DESTINATION_FINAL_BALANCE" "$DESTINATION_FINAL_BALANCE" + +ESCROW_FINAL_BALANCE=$(starkli balance --raw $ESCROW_CONTRACT_ADDRESS) +EXPECTED_ESCROW_FINAL_BALANCE=0 +assert "[[ $ESCROW_FINAL_BALANCE -eq $EXPECTED_ESCROW_FINAL_BALANCE ]]" "Escrow balance" "$EXPECTED_ESCROW_FINAL_BALANCE" "$ESCROW_FINAL_BALANCE" + +MM_FINAL_BALANCE=$(starkli balance --raw $MM_SN_WALLET_ADDR) +EXPECTED_MM_FINAL_BALANCE=1001000025000000000000 +assert "[[ $MM_FINAL_BALANCE -eq $EXPECTED_MM_FINAL_BALANCE ]]" "MM balance" "$EXPECTED_MM_FINAL_BALANCE" "$MM_FINAL_BALANCE" diff --git a/.github/workflows/scripts/set_order.sh b/.github/workflows/scripts/set_order.sh new file mode 100755 index 00000000..373899e7 --- /dev/null +++ b/.github/workflows/scripts/set_order.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +. contracts/utils/colors.sh #for ANSI colors + +#fee=24044002524012 +FEE=25000000000000 +APPROVE_AMOUNT=$((${AMOUNT}+${FEE})) + +echo -e "${GREEN}\n=> [SN] Making transfer to Escrow${COLOR_RESET}" + +starkli invoke \ + $NATIVE_TOKEN_ETH_STARKNET approve $ESCROW_CONTRACT_ADDRESS u256:$APPROVE_AMOUNT \ + / $ESCROW_CONTRACT_ADDRESS set_order 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \ + u256:$AMOUNT u256:$FEE --private-key $STARKNET_PRIVATE_KEY --account $STARKNET_ACCOUNT diff --git a/.github/workflows/scripts/setup_katana_account.sh b/.github/workflows/scripts/setup_katana_account.sh new file mode 100755 index 00000000..44179a76 --- /dev/null +++ b/.github/workflows/scripts/setup_katana_account.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +. contracts/utils/colors.sh #for ANSI colors + +source .github/workflows/katana/katana.env +set -e + +export STARKNET_ACCOUNT=$ACCOUNT_SRC +export STARKNET_RPC=$RPC_URL + +# Check if the JSON file exists +if [ ! -f "$ACCOUNT_SRC" ]; then + $(starkli account fetch --output $ACCOUNT_SRC $ACCOUNT_ADDRESS) + echo -e "$GREEN\n==> Katana JSON account file created at: $ACCOUNT_SRC$RESET" +else + echo -e "$GREEN\n==> Katana JSON account file already exists at: $ACCOUNT_SRC$RESET" +fi diff --git a/.github/workflows/scripts/transfer.sh b/.github/workflows/scripts/transfer.sh new file mode 100755 index 00000000..c04c5720 --- /dev/null +++ b/.github/workflows/scripts/transfer.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +. contracts/utils/colors.sh #for ANSI colors + +DESTINATION_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +DESTINATION_ADDRESS_UINT=642829559307850963015472508762062935916233390536 + +echo -e "${GREEN}\n=> [SN] Making transfer to Destination account${COLOR_RESET}" # 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 -> 642829559307850963015472508762062935916233390536 + +MM_INITIAL_BALANCE=$(cast balance --rpc-url $ETH_RPC_URL --ether $MM_ETHEREUM_WALLET) +DESTINATION_INITIAL_BALANCE=$(cast balance --rpc-url $ETH_RPC_URL --ether $DESTINATION_ADDRESS) +echo "Initial MM balance: $MM_INITIAL_BALANCE" +echo "Initial Destination balance: $DESTINATION_INITIAL_BALANCE" + +echo "Transferring $AMOUNT to $DESTINATION_ADDRESS" +cast send --rpc-url $ETH_RPC_URL --private-key $ETH_PRIVATE_KEY \ + $YAB_TRANSFER_PROXY_ADDRESS "transfer(uint256, uint256, uint256)" \ + "0" "$DESTINATION_ADDRESS_UINT" "$AMOUNT" \ + --value $AMOUNT >> /dev/null + +MM_FINAL_BALANCE=$(cast balance --rpc-url $ETH_RPC_URL --ether $MM_ETHEREUM_WALLET) +DESTINATION_FINAL_BALANCE=$(cast balance --rpc-url $ETH_RPC_URL --ether $DESTINATION_ADDRESS) +echo "Final MM balance: $MM_FINAL_BALANCE" +echo "Final Destination balance: $DESTINATION_FINAL_BALANCE" diff --git a/.github/workflows/scripts/withdraw.sh b/.github/workflows/scripts/withdraw.sh new file mode 100755 index 00000000..1b370cda --- /dev/null +++ b/.github/workflows/scripts/withdraw.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +. contracts/utils/colors.sh #for ANSI colors + +echo -e "${GREEN}\n=> [SN] Making withdraw${COLOR_RESET}" # 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 -> 642829559307850963015472508762062935916233390536 + +ESCROW_INITIAL_BALANCE=$(starkli balance $ESCROW_CONTRACT_ADDRESS) +MM_INITIAL_BALANCE=$(starkli balance $MM_SN_WALLET_ADDR) +echo "Initial Escrow balance: $ESCROW_INITIAL_BALANCE" +echo "Initial MM balance: $MM_INITIAL_BALANCE" + +echo "Withdrawing $AMOUNT" +cast send --rpc-url $ETH_RPC_URL --private-key $ETH_PRIVATE_KEY \ + $YAB_TRANSFER_PROXY_ADDRESS "withdraw(uint256, uint256, uint256)" \ + "0" "642829559307850963015472508762062935916233390536" "$AMOUNT" \ + --value $AMOUNT >> /dev/null + +sleep 15 + +starkli call $ESCROW_CONTRACT_ADDRESS get_order_used u256:0 + +ESCROW_FINAL_BALANCE=$(starkli balance $ESCROW_CONTRACT_ADDRESS) +MM_FINAL_BALANCE=$(starkli balance $MM_SN_WALLET_ADDR) +echo "Final Escrow balance: $ESCROW_FINAL_BALANCE" +echo "Final MM balance: $MM_FINAL_BALANCE" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 7d55ba1f..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Integration Test -on: - push: - branches: - - master - pull_request: - branches: - - master - workflow_dispatch: - -defaults: - run: - shell: bash - -jobs: - test: - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:latest - env: - POSTGRES_DB: postgres_db # optional (defaults to `postgres`) - POSTGRES_PASSWORD: postgres_password # required - POSTGRES_PORT: 5432 # optional (defaults to `5432`) - POSTGRES_USER: postgres_user # optional (defaults to `postgres`) - # `POSTGRES_HOST` is `localhost` - ports: - - 5432:5432 # maps tcp port 5432 on service container to the host - options: >- # set health checks to wait until postgres has started - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: 3.10 - architecture: x64 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r mm-bot/requirements.txt - - - name: Install PSQL - run: | - apt-get update - apt-get install --yes postgresql-client - - - name: Create tables - run: | - psql -h localhost -d postgres_db -U postgres_user -f mm-bot/resources/schema.sql - psql -h localhost -d postgres_db -U postgres_user -c "SELECT * FROM block;" - env: - PGPASSWORD: postgres_password - - - name: Install scarb - uses: software-mansion/setup-scarb@v1 - with: - scarb-version: "0.7.0" - - - name: Install starkliup - run: curl https://get.starkli.sh | sh - - - name: Install Starkli - run: | - /home/runner/.config/.starkli/bin/starkliup --version 0.1.20 - sudo mv /home/runner/.config/.starkli/bin/starkli /usr/local/bin/ - - - name: Setup Katana .env - run: | - cp .github/workflows/katana/katana.env .env - - - name: Download Katana - run: | - wget https://github.com/dojoengine/dojo/releases/download/v0.3.1/dojo_v0.3.1_linux_amd64.tar.gz - tar -xzvf dojo_v0.3.1_linux_amd64.tar.gz - rm sozo torii dojo-language-server diff --git a/contracts/cairo/.env.test b/contracts/cairo/.env.test new file mode 100644 index 00000000..a61ed383 --- /dev/null +++ b/contracts/cairo/.env.test @@ -0,0 +1,9 @@ +STARKNET_ACCOUNT=/home/runner/.config/.starkli/account_katana.json +STARKNET_ACCOUNT_ADDRESS=0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 +STARKNET_PRIVATE_KEY=0x1800000000300000180000000000030000000000003006001800006600 +SN_RPC_URL=http://0.0.0.0:5050 +SN_ESCROW_OWNER=0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 +MM_SN_WALLET_ADDR=0x5686a647a9cdd63ade617e0baf3b364856b813b508f03903eb58a7e622d5855 +WITHDRAW_NAME=withdraw +MM_ETHEREUM_WALLET=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +NATIVE_TOKEN_ETH_STARKNET=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 diff --git a/contracts/cairo/deploy.sh b/contracts/cairo/deploy.sh index 5f4c3dbd..c98f4be0 100755 --- a/contracts/cairo/deploy.sh +++ b/contracts/cairo/deploy.sh @@ -6,9 +6,9 @@ if [ -z "$STARKNET_ACCOUNT" ]; then echo "STARKNET_ACCOUNT Variable is empty. Aborting execution.\n" exit 1 fi -if [ -z "$STARKNET_KEYSTORE" ]; then +if [ -z "$STARKNET_KEYSTORE" ] && [ -z "$STARKNET_PRIVATE_KEY" ]; then echo "\n${RED}ERROR:${COLOR_RESET}" - echo "STARKNET_KEYSTORE Variable is empty. Aborting execution.\n" + echo "STARKNET_KEYSTORE and STARKNET_PRIVATE_KEY Variables are empty. Aborting execution.\n" exit 1 fi if [ -z "$MM_SN_WALLET_ADDR" ]; then @@ -35,7 +35,9 @@ fi echo "${GREEN}\n=> [SN] Declaring Escrow${COLOR_RESET}" ESCROW_CLASS_HASH=$(starkli declare \ - --account $STARKNET_ACCOUNT --keystore $STARKNET_KEYSTORE \ + --account $STARKNET_ACCOUNT \ + $(if [ -n "$STARKNET_KEYSTORE" ]; then echo "--keystore $STARKNET_KEYSTORE"; fi) \ + $(if [ -n "$STARKNET_PRIVATE_KEY" ]; then echo "--private-key $STARKNET_PRIVATE_KEY"; fi) \ --watch contracts/cairo/target/dev/yab_Escrow.contract_class.json) @@ -62,7 +64,9 @@ printf "${PINK}[ETH] Market Maker ETH Wallet: $MM_ETHEREUM_WALLET${COLOR_RESET}\ printf "${GREEN}\n=> [SN] Deploying Escrow${COLOR_RESET}\n" ESCROW_CONTRACT_ADDRESS=$(starkli deploy \ - --account $STARKNET_ACCOUNT --keystore $STARKNET_KEYSTORE \ + --account $STARKNET_ACCOUNT \ + $(if [ -n "$STARKNET_KEYSTORE" ]; then echo "--keystore $STARKNET_KEYSTORE"; fi) \ + $(if [ -n "$STARKNET_PRIVATE_KEY" ]; then echo "--private-key $STARKNET_PRIVATE_KEY"; fi) \ --watch $ESCROW_CLASS_HASH \ $SN_ESCROW_OWNER \ $YAB_TRANSFER_PROXY_ADDRESS \ diff --git a/contracts/cairo/src/escrow.cairo b/contracts/cairo/src/escrow.cairo index 651f2542..95e48822 100644 --- a/contracts/cairo/src/escrow.cairo +++ b/contracts/cairo/src/escrow.cairo @@ -20,7 +20,7 @@ trait IEscrow { fn get_eth_transfer_contract(self: @ContractState) -> EthAddress; fn get_mm_ethereum_contract(self: @ContractState) -> EthAddress; fn get_mm_starknet_contract(self: @ContractState) -> ContractAddress; - + fn set_eth_transfer_contract(ref self: ContractState, new_contract: EthAddress); fn set_mm_ethereum_contract(ref self: ContractState, new_contract: EthAddress); fn set_mm_starknet_contract(ref self: ContractState, new_contract: ContractAddress); @@ -114,7 +114,7 @@ mod Escrow { #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage, + upgradeable: UpgradeableComponent::Storage, #[substorage(v0)] pausable: PausableComponent::Storage } @@ -217,7 +217,7 @@ mod Escrow { fn set_mm_starknet_contract(ref self: ContractState, new_contract: ContractAddress) { self.pausable.assert_not_paused(); - self.ownable.assert_only_owner(); + self.ownable.assert_only_owner(); self.mm_starknet_wallet.write(new_contract); } diff --git a/contracts/cairo/src/escrow_herodotus.cairo b/contracts/cairo/src/escrow_herodotus.cairo new file mode 100644 index 00000000..9b78e6fd --- /dev/null +++ b/contracts/cairo/src/escrow_herodotus.cairo @@ -0,0 +1,309 @@ +use starknet::{ContractAddress, ClassHash, EthAddress}; + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct Order { + recipient_address: EthAddress, + amount: u256, + fee: u256 +} + +#[starknet::interface] +trait IEscrow { + fn get_order(self: @ContractState, order_id: u256) -> Order; + + fn set_order(ref self: ContractState, order: Order) -> u256; + + fn cancel_order(ref self: ContractState, order_id: u256); + + fn get_order_used(self: @ContractState, order_id: u256) -> bool; + + fn get_order_fee(self: @ContractState, order_id: u256) -> u256; + + fn withdraw(ref self: ContractState, order_id: u256, block: u256, slot: u256); + + fn get_herodotus_facts_registry_contract(self: @ContractState) -> ContractAddress; + fn get_eth_transfer_contract(self: @ContractState) -> EthAddress; + fn get_mm_ethereum_contract(self: @ContractState) -> EthAddress; + fn get_mm_starknet_contract(self: @ContractState) -> ContractAddress; + fn set_herodotus_facts_registry_contract( + ref self: ContractState, new_contract: ContractAddress + ); + fn set_eth_transfer_contract(ref self: ContractState, new_contract: EthAddress); + fn set_mm_ethereum_contract(ref self: ContractState, new_contract: EthAddress); + fn set_mm_starknet_contract(ref self: ContractState, new_contract: ContractAddress); +} + +#[starknet::contract] +mod Escrow { + use super::{IEscrow, Order}; + + use openzeppelin::{ + access::ownable::OwnableComponent, + upgrades::{UpgradeableComponent, interface::IUpgradeable} + }; + use starknet::{ + ContractAddress, EthAddress, ClassHash, get_caller_address, get_contract_address, + get_block_timestamp + }; + + use yab::interfaces::IERC20::{IERC20Dispatcher, IERC20DispatcherTrait}; + use yab::interfaces::IEVMFactsRegistry::{ + IEVMFactsRegistryDispatcher, IEVMFactsRegistryDispatcherTrait + }; + + /// Components + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + /// (Ownable) + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + /// (Upgradeable) + impl InternalImpl = UpgradeableComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Withdraw: Withdraw, + SetOrder: SetOrder, + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event + } + + #[derive(Drop, starknet::Event)] + struct SetOrder { + order_id: u256, + recipient_address: EthAddress, + amount: u256, + fee: u256 + } + + #[derive(Drop, starknet::Event)] + struct Withdraw { + order_id: u256, + address: ContractAddress, + amount: u256, + } + + #[storage] + struct Storage { + current_order_id: u256, + orders: LegacyMap::, + orders_used: LegacyMap::, + orders_senders: LegacyMap::, + orders_timestamps: LegacyMap::, + herodotus_facts_registry_contract: ContractAddress, + eth_transfer_contract: EthAddress, // our transfer contract in L1 + mm_ethereum_wallet: EthAddress, + mm_starknet_wallet: ContractAddress, + native_token_eth_starknet: ContractAddress, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + herodotus_facts_registry_contract: ContractAddress, + eth_transfer_contract: EthAddress, + mm_ethereum_wallet: EthAddress, + mm_starknet_wallet: ContractAddress, + native_token_eth_starknet: ContractAddress + ) { + self.ownable.initializer(owner); + + self.current_order_id.write(0); + self.herodotus_facts_registry_contract.write(herodotus_facts_registry_contract); + self.eth_transfer_contract.write(eth_transfer_contract); + self.mm_ethereum_wallet.write(mm_ethereum_wallet); + self.mm_starknet_wallet.write(mm_starknet_wallet); + self.native_token_eth_starknet.write(native_token_eth_starknet); + } + + #[external(v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable._upgrade(new_class_hash); + } + } + + #[external(v0)] + impl Escrow of IEscrow { + fn get_order(self: @ContractState, order_id: u256) -> Order { + self.orders.read(order_id) + } + + fn set_order(ref self: ContractState, order: Order) -> u256 { + assert(order.amount > 0, 'Amount must be greater than 0'); + + let mut order_id = self.current_order_id.read(); + self.orders.write(order_id, order); + self.orders_used.write(order_id, false); + self.orders_senders.write(order_id, get_caller_address()); + self.orders_timestamps.write(order_id, get_block_timestamp()); + let payment_amount = order.amount + order.fee; + + IERC20Dispatcher { contract_address: self.native_token_eth_starknet.read() } + .transferFrom(get_caller_address(), get_contract_address(), payment_amount); + + self + .emit( + SetOrder { + order_id, + recipient_address: order.recipient_address, + amount: order.amount, + fee: order.fee + } + ); + + self.current_order_id.write(order_id + 1); + order_id + } + + fn cancel_order(ref self: ContractState, order_id: u256) { + assert(!self.orders_used.read(order_id), 'Order already withdrawed'); + assert( + get_block_timestamp() - self.orders_timestamps.read(order_id) < 43200, + 'Didnt passed enough time' + ); + + let sender = self.orders_senders.read(order_id); + assert(sender == get_caller_address(), 'Only sender allowed'); + let order = self.orders.read(order_id); + let payment_amount = order.amount + order.fee; + + IERC20Dispatcher { contract_address: self.native_token_eth_starknet.read() } + .transfer(sender, payment_amount); + } + + fn get_order_used(self: @ContractState, order_id: u256) -> bool { + self.orders_used.read(order_id) + } + + fn get_order_fee(self: @ContractState, order_id: u256) -> u256 { + let order: Order = self.orders.read(order_id); + order.fee + } + + fn withdraw(ref self: ContractState, order_id: u256, block: u256, slot: u256) { + assert(!self.orders_used.read(order_id), 'Order already withdrawed'); + + // Read transfer info from the facts registry + // struct TransferInfo { + // uint256 destAddress; + // uint256 amount; + // bool isUsed; + // } + + let mut slot_1 = slot.clone(); + slot_1 += 1; + + let slot_0 = slot; + + // Slot n contains the address of the recipient + let slot_0_value = IEVMFactsRegistryDispatcher { + contract_address: self.herodotus_facts_registry_contract.read() + } + .get_slot_value(self.eth_transfer_contract.read().into(), block, slot_0) + .unwrap(); + + let recipient_address: felt252 = slot_0_value + .try_into() + .expect('Invalid address parse felt252'); + let recipient_address: EthAddress = recipient_address + .try_into() + .expect('Invalid address parse EthAddres'); + + let order = self.orders.read(order_id); + assert(order.recipient_address == recipient_address, 'recipient_address not match L1'); + + // Slot n+1 contains the amount and isUsed + let amount = IEVMFactsRegistryDispatcher { + contract_address: self.herodotus_facts_registry_contract.read() + } + .get_slot_value(self.eth_transfer_contract.read().into(), block, slot_1) + .unwrap(); + + assert(order.amount == amount, 'amount not match L1'); + + self.orders_used.write(order_id, true); + let payment_amount = order.amount + order.fee; + + IERC20Dispatcher { contract_address: self.native_token_eth_starknet.read() } + .transfer(self.mm_starknet_wallet.read(), payment_amount); + + self.emit(Withdraw { order_id, address: self.mm_starknet_wallet.read(), amount }); + } + + fn get_herodotus_facts_registry_contract(self: @ContractState) -> ContractAddress { + self.herodotus_facts_registry_contract.read() + } + + fn get_eth_transfer_contract(self: @ContractState) -> EthAddress { + self.eth_transfer_contract.read() + } + + fn get_mm_ethereum_contract(self: @ContractState) -> EthAddress { + self.mm_ethereum_wallet.read() + } + + fn get_mm_starknet_contract(self: @ContractState) -> ContractAddress { + self.mm_starknet_wallet.read() + } + + fn set_herodotus_facts_registry_contract( + ref self: ContractState, new_contract: ContractAddress + ) { + self.ownable.assert_only_owner(); + self.herodotus_facts_registry_contract.write(new_contract); + } + + fn set_eth_transfer_contract(ref self: ContractState, new_contract: EthAddress) { + self.ownable.assert_only_owner(); + self.eth_transfer_contract.write(new_contract); + } + + fn set_mm_ethereum_contract(ref self: ContractState, new_contract: EthAddress) { + self.ownable.assert_only_owner(); + self.mm_ethereum_wallet.write(new_contract); + } + + fn set_mm_starknet_contract(ref self: ContractState, new_contract: ContractAddress) { + self.ownable.assert_only_owner(); + self.mm_starknet_wallet.write(new_contract); + } + } + + #[l1_handler] + fn withdraw_fallback( + ref self: ContractState, + from_address: felt252, + order_id: u256, + recipient_address: EthAddress, + amount: u256 + ) { + let eth_transfer_contract_felt: felt252 = self.eth_transfer_contract.read().into(); + assert(eth_transfer_contract_felt == from_address, 'Only ETH_TRANSFER_CONTRACT'); + assert(!self.orders_used.read(order_id), 'Order already withdrawed'); + + let order = self.orders.read(order_id); + assert(order.recipient_address == recipient_address, 'recipient_address not match L1'); + assert(order.amount == amount, 'amount not match L1'); + + self.orders_used.write(order_id, true); + let payment_amount = order.amount + order.fee; + + IERC20Dispatcher { contract_address: self.native_token_eth_starknet.read() } + .transfer(self.mm_starknet_wallet.read(), payment_amount); + + self.emit(Withdraw { order_id, address: self.mm_starknet_wallet.read(), amount }); + } +} diff --git a/contracts/cairo/src/mocks/mock_EscrowV2.cairo b/contracts/cairo/src/mocks/mock_EscrowV2.cairo new file mode 100644 index 00000000..2a9c8c49 --- /dev/null +++ b/contracts/cairo/src/mocks/mock_EscrowV2.cairo @@ -0,0 +1,337 @@ +use starknet::{ContractAddress, ClassHash, EthAddress}; + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct Order { + recipient_address: EthAddress, + amount: u256, + fee: u256 +} + +#[starknet::interface] +trait IEscrowV2 { + fn get_orderV2(self: @ContractState, order_id: u256) -> Order; + + fn set_orderV2(ref self: ContractState, order: Order) -> u256; + + fn cancel_order(ref self: ContractState, order_id: u256); + + fn get_order_used(self: @ContractState, order_id: u256) -> bool; + + fn get_order_fee(self: @ContractState, order_id: u256) -> u256; + + fn withdraw(ref self: ContractState, order_id: u256, block: u256, slot: u256); + + fn get_herodotus_facts_registry_contract(self: @ContractState) -> ContractAddress; + fn get_eth_transfer_contract(self: @ContractState) -> EthAddress; + fn get_mm_ethereum_contract(self: @ContractState) -> EthAddress; + fn get_mm_starknet_contract(self: @ContractState) -> ContractAddress; + fn set_herodotus_facts_registry_contract( + ref self: ContractState, new_contract: ContractAddress + ); + fn get_mm_starknet_contractv2(self: @ContractState) -> ContractAddress; + fn set_herodotus_facts_registry_contractv2( + ref self: ContractState, new_contract: ContractAddress + ); + fn set_eth_transfer_contract(ref self: ContractState, new_contract: EthAddress); + fn set_mm_ethereum_contract(ref self: ContractState, new_contract: EthAddress); + fn set_mm_starknet_contract(ref self: ContractState, new_contract: ContractAddress); + + fn new_mock_function(self: @ContractState) -> bool; +} + +#[starknet::contract] +mod EscrowV2 { + use super::{IEscrowV2, Order}; + + use openzeppelin::{ + access::ownable::OwnableComponent, + upgrades::{UpgradeableComponent, interface::IUpgradeable} + }; + use starknet::{ + ContractAddress, EthAddress, ClassHash, get_caller_address, get_contract_address, + get_block_timestamp + }; + + use yab::interfaces::IERC20::{IERC20Dispatcher, IERC20DispatcherTrait}; + use yab::interfaces::IEVMFactsRegistry::{ + IEVMFactsRegistryDispatcher, IEVMFactsRegistryDispatcherTrait + }; + + /// Components + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + /// (Ownable) + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + /// (Upgradeable) + impl InternalImpl = UpgradeableComponent::InternalImpl; + + // https://github.com/starknet-io/starknet-addresses + // MAINNET = GOERLI = GOERLI2 + // 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 + // const NATIVE_TOKEN: felt252 = + // 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Withdraw: Withdraw, + SetOrder: SetOrder, + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event + } + + #[derive(Drop, starknet::Event)] + struct SetOrder { + order_id: u256, + recipient_address: EthAddress, + amount: u256, + fee: u256 + } + + #[derive(Drop, starknet::Event)] + struct Withdraw { + order_id: u256, + address: ContractAddress, + amount: u256, + } + + #[storage] + struct Storage { + current_order_id: u256, + orders: LegacyMap::, + orders_used: LegacyMap::, + orders_senders: LegacyMap::, + orders_timestamps: LegacyMap::, + herodotus_facts_registry_contract: ContractAddress, + eth_transfer_contract: EthAddress, // our transfer contract in L1 + mm_ethereum_wallet: EthAddress, + mm_starknet_wallet: ContractAddress, + native_token_eth_starknet: ContractAddress, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + herodotus_facts_registry_contract: ContractAddress, + eth_transfer_contract: EthAddress, + mm_ethereum_wallet: EthAddress, + mm_starknet_wallet: ContractAddress, + native_token_eth_starknet: ContractAddress + ) { + self.ownable.initializer(owner); + + self.current_order_id.write(0); + self.herodotus_facts_registry_contract.write(herodotus_facts_registry_contract); + self.eth_transfer_contract.write(eth_transfer_contract); + self.mm_ethereum_wallet.write(mm_ethereum_wallet); + self.mm_starknet_wallet.write(mm_starknet_wallet); + self.native_token_eth_starknet.write(native_token_eth_starknet); + } + + #[external(v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable._upgrade(new_class_hash); + } + } + + #[external(v0)] + impl Escrow of IEscrowV2 { + fn get_orderV2(self: @ContractState, order_id: u256) -> Order { + self.orders.read(order_id) + } + + fn set_orderV2(ref self: ContractState, order: Order) -> u256 { + assert(order.amount > 0, 'Amount must be greater than 0'); + + let mut order_id = self.current_order_id.read(); + self.orders.write(order_id, order); + self.orders_used.write(order_id, false); + self.orders_senders.write(order_id, get_caller_address()); + self.orders_timestamps.write(order_id, get_block_timestamp()); + let payment_amount = order.amount + order.fee; + + IERC20Dispatcher { contract_address: self.native_token_eth_starknet.read() } + .transferFrom(get_caller_address(), get_contract_address(), payment_amount); + + self + .emit( + SetOrder { + order_id, + recipient_address: order.recipient_address, + amount: order.amount, + fee: order.fee + } + ); + + self.current_order_id.write(order_id + 1); + order_id + } + + fn cancel_order(ref self: ContractState, order_id: u256) { + assert(!self.orders_used.read(order_id), 'Order already withdrawed'); + assert( + get_block_timestamp() - self.orders_timestamps.read(order_id) < 43200, + 'Didnt passed enough time' + ); + + let sender = self.orders_senders.read(order_id); + assert(sender == get_caller_address(), 'Only sender allowed'); + let order = self.orders.read(order_id); + let payment_amount = order.amount + order.fee; + + IERC20Dispatcher { contract_address: self.native_token_eth_starknet.read() } + .transfer(sender, payment_amount); + } + + fn get_order_used(self: @ContractState, order_id: u256) -> bool { + self.orders_used.read(order_id) + } + + fn get_order_fee(self: @ContractState, order_id: u256) -> u256 { + let order: Order = self.orders.read(order_id); + order.fee + } + + fn withdraw(ref self: ContractState, order_id: u256, block: u256, slot: u256) { + assert(!self.orders_used.read(order_id), 'Order already withdrawed'); + + // Read transfer info from the facts registry + // struct TransferInfo { + // uint256 destAddress; + // uint256 amount; + // bool isUsed; + // } + + let mut slot_1 = slot.clone(); + slot_1 += 1; + + let slot_0 = slot; + + // Slot n contains the address of the recipient + let slot_0_value = IEVMFactsRegistryDispatcher { + contract_address: self.herodotus_facts_registry_contract.read() + } + .get_slot_value(self.eth_transfer_contract.read().into(), block, slot_0) + .unwrap(); + + let recipient_address: felt252 = slot_0_value + .try_into() + .expect('Invalid address parse felt252'); + let recipient_address: EthAddress = recipient_address + .try_into() + .expect('Invalid address parse EthAddres'); + + let order = self.orders.read(order_id); + assert(order.recipient_address == recipient_address, 'recipient_address not match L1'); + + // Slot n+1 contains the amount and isUsed + let amount = IEVMFactsRegistryDispatcher { + contract_address: self.herodotus_facts_registry_contract.read() + } + .get_slot_value(self.eth_transfer_contract.read().into(), block, slot_1) + .unwrap(); + + assert(order.amount == amount, 'amount not match L1'); + + self.orders_used.write(order_id, true); + let payment_amount = order.amount + order.fee; + + IERC20Dispatcher { contract_address: self.native_token_eth_starknet.read() } + .transfer(self.mm_starknet_wallet.read(), payment_amount); + + self.emit(Withdraw { order_id, address: self.mm_starknet_wallet.read(), amount }); + } + + fn get_herodotus_facts_registry_contract(self: @ContractState) -> ContractAddress { + self.herodotus_facts_registry_contract.read() + } + + fn get_eth_transfer_contract(self: @ContractState) -> EthAddress { + self.eth_transfer_contract.read() + } + + fn get_mm_ethereum_contract(self: @ContractState) -> EthAddress { + self.mm_ethereum_wallet.read() + } + + fn get_mm_starknet_contract(self: @ContractState) -> ContractAddress { + self.mm_starknet_wallet.read() + } + + fn set_herodotus_facts_registry_contract( + ref self: ContractState, new_contract: ContractAddress + ) { + self.ownable.assert_only_owner(); + self.herodotus_facts_registry_contract.write(new_contract); + } + + fn get_mm_starknet_contractv2(self: @ContractState) -> ContractAddress { + self.mm_starknet_wallet.read() + } + + fn set_herodotus_facts_registry_contractv2( + ref self: ContractState, new_contract: ContractAddress + ) { + self.ownable.assert_only_owner(); + self.herodotus_facts_registry_contract.write(new_contract); + } + + fn set_eth_transfer_contract(ref self: ContractState, new_contract: EthAddress) { + self.ownable.assert_only_owner(); + self.eth_transfer_contract.write(new_contract); + } + + fn set_mm_ethereum_contract(ref self: ContractState, new_contract: EthAddress) { + self.ownable.assert_only_owner(); + self.mm_ethereum_wallet.write(new_contract); + } + + fn set_mm_starknet_contract(ref self: ContractState, new_contract: ContractAddress) { + self.ownable.assert_only_owner(); + self.mm_starknet_wallet.write(new_contract); + } + + fn new_mock_function(self: @ContractState) -> bool{ + true + } + + } + + #[l1_handler] + fn withdraw_fallback( + ref self: ContractState, + from_address: felt252, + order_id: u256, + recipient_address: EthAddress, + amount: u256 + ) { + let eth_transfer_contract_felt: felt252 = self.eth_transfer_contract.read().into(); + assert(eth_transfer_contract_felt == from_address, 'Only ETH_TRANSFER_CONTRACT'); + assert(!self.orders_used.read(order_id), 'Order already withdrawed'); + + let order = self.orders.read(order_id); + assert(order.recipient_address == recipient_address, 'recipient_address not match L1'); + assert(order.amount == amount, 'amount not match L1'); + + self.orders_used.write(order_id, true); + let payment_amount = order.amount + order.fee; + + IERC20Dispatcher { contract_address: self.native_token_eth_starknet.read() } + .transfer(self.mm_starknet_wallet.read(), payment_amount); + + self.emit(Withdraw { order_id, address: self.mm_starknet_wallet.read(), amount }); + } +} diff --git a/contracts/cairo/src/tests/test_escrow_allowance.cairo b/contracts/cairo/src/tests/test_escrow_allowance.cairo index 14b1a973..555169eb 100644 --- a/contracts/cairo/src/tests/test_escrow_allowance.cairo +++ b/contracts/cairo/src/tests/test_escrow_allowance.cairo @@ -79,7 +79,7 @@ mod Escrow { let address = escrow.deploy(@calldata).unwrap(); return IEscrowDispatcher { contract_address: address }; } - + fn deploy_erc20( name: felt252, symbol: felt252, initial_supply: u256, recipent: ContractAddress ) -> IERC20Dispatcher { diff --git a/contracts/solidity/.env.test b/contracts/solidity/.env.test new file mode 100644 index 00000000..e37f6425 --- /dev/null +++ b/contracts/solidity/.env.test @@ -0,0 +1,6 @@ +ETH_RPC_URL=http://127.0.0.1:8545 +ETHERSCAN_API_KEY=0x1 +ETH_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +SN_MESSAGING_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3 +MM_ETHEREUM_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +SKIP_VERIFY=true diff --git a/contracts/solidity/deploy.sh b/contracts/solidity/deploy.sh index 6b151135..017cfb60 100755 --- a/contracts/solidity/deploy.sh +++ b/contracts/solidity/deploy.sh @@ -5,7 +5,7 @@ cd contracts/solidity printf "${GREEN}\n=> [ETH] Deploying ERC1967Proxy & YABTransfer ${COLOR_RESET}\n" -RESULT_LOG=$(forge script ./script/Deploy.s.sol --rpc-url $ETH_RPC_URL --broadcast --verify) +RESULT_LOG=$(forge script ./script/Deploy.s.sol --rpc-url $ETH_RPC_URL --broadcast ${SKIP_VERIFY:---verify}) # echo "$RESULT_LOG" #uncomment this line for debugging in detail # Getting result addresses From 995700ca93fe602d514ca53e5192fad3829dbc41 Mon Sep 17 00:00:00 2001 From: Julian Arce <52429267+JuArce@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:38:39 -0300 Subject: [PATCH 3/3] fix: run integration-test on push to main instead of master (#141) --- .github/workflows/integration-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 0bae806c..8bb56bd2 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -2,7 +2,7 @@ name: Integration Test on: push: branches: - - master + - main pull_request: types: [opened, synchronize, reopened] workflow_dispatch: