From 2395950301ec2fdb3e3352c471314c642cccfd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Wed, 28 Aug 2024 18:06:56 +0200 Subject: [PATCH] Run end2end tests on sn sepolia (no messaging) (#1360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Time spent on this PR: 0.5 ## Pull request type Please check the type of change your PR introduces: - [x] Bugfix - [ ] Feature - [ ] Code style update (formatting, renaming) - [ ] Refactoring (no functional changes, no api changes) - [ ] Build related changes - [ ] Documentation content changes - [ ] Other (please describe): ## What is the current behavior? After these minor fixes, I've been able to run Counter and PlainOpcodes tests. For running the whole test suite, some more updates need to be done - [x] get more ETH for the SN deployer account (like 100) or update values in tests - [ ] update sn messaging system fixture to use the real sn messaging contract on sepolia ## What is the new behavior? - - - This change is [Reviewable](https://reviewable.io/reviews/kkrt-labs/kakarot/1360) --- .github/workflows/ci.yml | 16 +- .github/workflows/release.yml | 2 +- .github/workflows/trunk-check.yaml | 2 +- .trunk/trunk.yaml | 1 + Makefile | 40 +--- cairo1_contracts/token/src/lib.cairo | 36 +--- .../token/src/starknet_token.cairo | 35 ++++ cairo1_contracts/utils/.gitignore | 1 + cairo1_contracts/utils/.tool-versions | 1 + cairo1_contracts/utils/Scarb.lock | 6 + cairo1_contracts/utils/Scarb.toml | 12 ++ .../utils/src/balance_sender.cairo | 39 ++++ cairo1_contracts/utils/src/lib.cairo | 3 + .../utils/src/universal_library_caller.cairo | 28 +++ .../starknet-sepolia/declarations.json | 22 +- deployments/starknet-sepolia/deployments.json | 24 ++- kakarot_scripts/constants.py | 82 ++++---- kakarot_scripts/deploy_kakarot.py | 46 ++--- kakarot_scripts/utils/kakarot.py | 23 ++- kakarot_scripts/utils/starknet.py | 194 ++++++++---------- {tests => kakarot_scripts}/utils/uint256.py | 0 .../CairoPrecompiles/EthStarknetBridge.sol | 48 +++++ tests/end_to_end/Solmate/conftest.py | 4 +- tests/end_to_end/Solmate/test_erc20.py | 33 +-- tests/end_to_end/conftest.py | 47 ++++- tests/end_to_end/test_kakarot.py | 4 +- .../kakarot/accounts/test_account_contract.py | 2 +- .../test_environmental_information.py | 2 +- .../test_stop_and_math_operations.py | 2 +- tests/src/kakarot/test_account.cairo | 6 +- tests/src/kakarot/test_account.py | 2 +- tests/src/kakarot/test_gas.cairo | 2 +- tests/src/kakarot/test_kakarot.cairo | 2 +- tests/src/kakarot/test_state.cairo | 2 +- tests/src/utils/test_bytes.py | 2 +- tests/src/utils/test_uint256.py | 2 +- tests/utils/helpers.py | 2 +- tests/utils/syscall_handler.py | 2 +- 38 files changed, 448 insertions(+), 329 deletions(-) create mode 100644 cairo1_contracts/token/src/starknet_token.cairo create mode 100644 cairo1_contracts/utils/.gitignore create mode 100644 cairo1_contracts/utils/.tool-versions create mode 100644 cairo1_contracts/utils/Scarb.lock create mode 100644 cairo1_contracts/utils/Scarb.toml create mode 100644 cairo1_contracts/utils/src/balance_sender.cairo create mode 100644 cairo1_contracts/utils/src/lib.cairo create mode 100644 cairo1_contracts/utils/src/universal_library_caller.cairo rename {tests => kakarot_scripts}/utils/uint256.py (100%) create mode 100644 solidity_contracts/src/CairoPrecompiles/EthStarknetBridge.sol diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47715f6ca..2571b4422 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v4 # Python setup - name: Set up Python 3.10.14 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.10.14 - name: Load cached Poetry installation @@ -51,7 +51,8 @@ jobs: - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: make setup - + - uses: asdf-vm/actions/install@v3 + - run: asdf install # Build artifacts - name: Compile all the cairo files run: make build @@ -70,7 +71,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.10.14 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.10.14 - name: Load cached Poetry installation @@ -123,7 +124,7 @@ jobs: KATANA_VERSION=$(grep -oP '^KATANA_VERSION = \K.*' Makefile) echo "katana_version=$KATANA_VERSION" >> "$GITHUB_OUTPUT" - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.10.14 - name: Load cached Poetry installation @@ -153,8 +154,7 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 with: version: nightly - - name: Install asdf & tools # For multiple versions of scarb - reads from .tool-versions and installs them - uses: asdf-vm/actions/install@v3 + - uses: asdf-vm/actions/install@v3 - name: Load cached katana id: cached-katana uses: actions/cache@v4 @@ -204,7 +204,7 @@ jobs: - name: Move ERC20 run: mv build/v0/ERC20.json build/common - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.10.14 - name: run tests @@ -228,7 +228,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.10.14 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.10.14 - name: Load cached Poetry installation diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c5c88db80..92e33dab5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.10.14 - name: Load cached Poetry installation diff --git a/.github/workflows/trunk-check.yaml b/.github/workflows/trunk-check.yaml index 2e0ec335f..0adad3aa0 100644 --- a/.github/workflows/trunk-check.yaml +++ b/.github/workflows/trunk-check.yaml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v3 - name: Set up Python 3.10.14 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.10.14 cache: pip diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index ac32e75b2..b5f0a28ca 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -75,6 +75,7 @@ lint: - resources* - tests/ef_tests/test_data - .katana/messaging_config.json + - deployments - linters: [solidity] paths: - solidity_contracts/src/UniswapV2/**/*.sol diff --git a/Makefile b/Makefile index 6f70432de..6e6ba2a7f 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ fetch-ssj-artifacts: setup: fetch-ssj-artifacts poetry install -test: build-sol build-cairo1 deploy +test: deploy poetry run pytest tests/src -m "not NoCI" --log-cli-level=INFO -n logical --seed 42 poetry run pytest tests/end_to_end --seed 42 @@ -46,7 +46,7 @@ test-unit: build-sol poetry run pytest tests/src -m "not NoCI" -n logical --seed 42 # run make run-nodes in other terminal -test-end-to-end: build-sol build-cairo1 deploy +test-end-to-end: deploy poetry run pytest tests/end_to_end --seed 42 deploy: build build-sol @@ -70,42 +70,6 @@ build-sol: git submodule update --init --recursive forge build --names --force -# Builds Cairo 1.0 contracts by iterating over subdirectories, -# compiling contracts, and copying the resulting .sierra.json (old versions) or .contract_class.json -# files to the ROOT_DIR/build/fixtures directory with appropriate file extensions. -build-cairo1: - @mkdir -p build/ssj - @for d in cairo1_contracts/*/ ; do \ - if [ "$$d" != "cairo1_contracts/build/" ]; then \ - echo "Building $$d"; \ - cd $$d; \ - scarb build; \ - for f in target/dev/*.sierra.json target/dev/*.contract_class.json target/dev/*.casm.json target/dev/*.compiled_contract_class.json; do \ - if [ -e "$$f" ]; then \ - case "$$f" in \ - *.sierra.json) \ - CONTRACT_NAME="$$(basename $$f | sed -E 's/^.*_([^_.]*)\.sierra\.json$$/\1/')"; \ - cp "$$f" "$(ROOT_DIR)/build/ssj/contracts_$$CONTRACT_NAME.contract_class.json"; \ - ;; \ - *.contract_class.json) \ - CONTRACT_NAME="$$(basename $$f | sed -E 's/^.*_([^_.]*)\.contract_class\.json$$/\1/')"; \ - cp "$$f" "$(ROOT_DIR)/build/ssj/contracts_$$CONTRACT_NAME.contract_class.json"; \ - ;; \ - *.casm.json) \ - CONTRACT_NAME="$$(basename $$f | sed -E 's/^.*_([^_.]*)\.casm\.json$$/\1/')"; \ - cp "$$f" "$(ROOT_DIR)/build/ssj/contracts_$$CONTRACT_NAME.compiled_contract_class.json"; \ - ;; \ - *.compiled_contract_class.json) \ - CONTRACT_NAME="$$(basename $$f | sed -E 's/^.*_([^_.]*)\.compiled_contract_class\.json$$/\1/')"; \ - cp "$$f" "$(ROOT_DIR)/build/ssj/contracts_$$CONTRACT_NAME.compiled_contract_class.json"; \ - ;; \ - esac; \ - fi; \ - done; \ - cd -; \ - fi; \ - done - install-katana: cargo install --git https://github.com/dojoengine/dojo --locked --tag "${KATANA_VERSION}" katana diff --git a/cairo1_contracts/token/src/lib.cairo b/cairo1_contracts/token/src/lib.cairo index 22a9f7028..9b8f06e6a 100644 --- a/cairo1_contracts/token/src/lib.cairo +++ b/cairo1_contracts/token/src/lib.cairo @@ -1,35 +1 @@ -#[starknet::contract] -mod StarknetToken { - use openzeppelin::token::erc20::ERC20Component; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20._mint(recipient, initial_supply); - } -} +mod starknet_token; diff --git a/cairo1_contracts/token/src/starknet_token.cairo b/cairo1_contracts/token/src/starknet_token.cairo new file mode 100644 index 000000000..22a9f7028 --- /dev/null +++ b/cairo1_contracts/token/src/starknet_token.cairo @@ -0,0 +1,35 @@ +#[starknet::contract] +mod StarknetToken { + use openzeppelin::token::erc20::ERC20Component; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { + let name = "MyToken"; + let symbol = "MTK"; + + self.erc20.initializer(name, symbol); + self.erc20._mint(recipient, initial_supply); + } +} diff --git a/cairo1_contracts/utils/.gitignore b/cairo1_contracts/utils/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/cairo1_contracts/utils/.gitignore @@ -0,0 +1 @@ +target diff --git a/cairo1_contracts/utils/.tool-versions b/cairo1_contracts/utils/.tool-versions new file mode 100644 index 000000000..179f2a8c2 --- /dev/null +++ b/cairo1_contracts/utils/.tool-versions @@ -0,0 +1 @@ +scarb 2.6.5 diff --git a/cairo1_contracts/utils/Scarb.lock b/cairo1_contracts/utils/Scarb.lock new file mode 100644 index 000000000..abda56939 --- /dev/null +++ b/cairo1_contracts/utils/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "utils" +version = "0.1.0" diff --git a/cairo1_contracts/utils/Scarb.toml b/cairo1_contracts/utils/Scarb.toml new file mode 100644 index 000000000..084e2cea0 --- /dev/null +++ b/cairo1_contracts/utils/Scarb.toml @@ -0,0 +1,12 @@ +[package] +name = "utils" +version = "0.1.0" +edition = "2023_11" + +[dependencies] +starknet = "2.6.4" + +[[target.starknet-contract]] +casm = true +sierra = true +casm-add-pythonic-hints = true diff --git a/cairo1_contracts/utils/src/balance_sender.cairo b/cairo1_contracts/utils/src/balance_sender.cairo new file mode 100644 index 000000000..0afe76564 --- /dev/null +++ b/cairo1_contracts/utils/src/balance_sender.cairo @@ -0,0 +1,39 @@ +use core::starknet::{get_caller_address, ContractAddress}; + + +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::contract] +pub mod BalanceSender { + use core::starknet::{get_caller_address, ContractAddress, ClassHash, get_contract_address, SyscallResult}; + use super::{IERC20Dispatcher, IERC20DispatcherTrait}; + use core::starknet::syscalls::{replace_class_syscall}; + + #[storage] + struct Storage {} + + #[external(v0)] + fn send_balance(self: @ContractState, token_address: ContractAddress, recipient: ContractAddress) -> bool { + let erc20_dispatcher = IERC20Dispatcher { contract_address: token_address }; + let balance = erc20_dispatcher.balance_of(get_contract_address()); + erc20_dispatcher.transfer(recipient, balance) + } + + #[external(v0)] + fn replace_class(ref self: ContractState, new_class: ClassHash) -> SyscallResult<()>{ + replace_class_syscall(new_class) + } +} diff --git a/cairo1_contracts/utils/src/lib.cairo b/cairo1_contracts/utils/src/lib.cairo new file mode 100644 index 000000000..bdd1426d0 --- /dev/null +++ b/cairo1_contracts/utils/src/lib.cairo @@ -0,0 +1,3 @@ +mod universal_library_caller; + +mod balance_sender; diff --git a/cairo1_contracts/utils/src/universal_library_caller.cairo b/cairo1_contracts/utils/src/universal_library_caller.cairo new file mode 100644 index 000000000..b404a3e23 --- /dev/null +++ b/cairo1_contracts/utils/src/universal_library_caller.cairo @@ -0,0 +1,28 @@ +use starknet::{ + SyscallResult, storage_access::StorageAddress, class_hash::ClassHash, +}; + + +#[starknet::interface] +pub trait IUniversalLibraryCaller { + fn library_call(self: @TContractState, class_hash: ClassHash, function_selector: felt252, calldata: Span) -> SyscallResult>; +} + +#[starknet::contract] +pub mod UniversalLibraryCaller { + use starknet::syscalls::library_call_syscall; + use starknet::{ + SyscallResult, storage_access::StorageAddress, class_hash::ClassHash, + }; + + + #[storage] + struct Storage {} + + #[abi(embed_v0)] + impl UniversalLibraryCallerImpl of super::IUniversalLibraryCaller { + fn library_call(self: @ContractState, class_hash: ClassHash, function_selector: felt252, calldata: Span) -> SyscallResult> { + library_call_syscall(class_hash, function_selector, calldata) + } + } +} diff --git a/deployments/starknet-sepolia/declarations.json b/deployments/starknet-sepolia/declarations.json index 77d9b7a69..c4c401168 100644 --- a/deployments/starknet-sepolia/declarations.json +++ b/deployments/starknet-sepolia/declarations.json @@ -1,9 +1,17 @@ { - "kakarot": "0x6aa7b2a71ce0e28301a6547b8eb38c5f4130d0091b5aa8a12c8f380efcbc4c4", - "account_contract": "0x56d311021950bf65ee500426e007b9e3ced0db97f9c1e0d29a9e03d79a9bf6c", - "uninitialized_account": "0x1d8b8047e26b484d3f6262d1967217d980d0f2dfc69afa5661492bd5bfe2954", - "EVM": "0x78e943202d567c81ec9b523e5121c15914210f915dd7bce69f09ceb5ae91934", - "OpenzeppelinAccount": "0x452189b6cd1ef66a7daef29cbffb77ce809cac95449687aabb169117c04e2f9", - "Cairo1Helpers": "0xff0ec0846982c93e48ed5130dba8efe5905c099d0ffe45c1fd777a97a2b71a", - "replace_class": "0x5cd1a33bc766f50965fe2343e6aec12a12c562b3bb653085b88dc6751b71682" + "account_contract": "0x6cb1275516c11f6c1f9d2758bd212d2c40d8136ebd353c316779b754a216d83", + "uninitialized_account_fixture": "0x2957ff0877441dddcd140e6af50a3d45712f4f7205a36a846110a70297036be", + "uninitialized_account": "0x45f7d0803659c3f58b5b6ba46f349178253dadabbfc6ab47fa1ba4bab4699f8", + "EVM": "0x1ce258b332ad964d0d0a472b7795615a84f25196b733a319e101b948f3064a8", + "OpenzeppelinAccount": "0x6153ccf69fd20f832c794df36e19135f0070d0576144f0b47f75a226e4be530", + "Cairo1Helpers": "0x28ece3751ecf5bdf2d791eb64a65bfb6a8816432b698870dba2f38a36101d58", + "Cairo1HelpersFixture": "0x4e7811d9bbba41193bd3c77d05c16f7aaa55dd1d601686b50f6fa0e3766a712", + "replace_class": "0xa187318c5e79b010cf45975f589f0a8d441fadde5b1e7ccad46501568437b5", + "Counter": "0x4fc47610d8c9ce0bcfc2f9e03658f0fbcd3e0a9c351a1aa59d465a33533a7c8", + "MockPragmaOracle": "0x675f00328ff84f127d71b179b3f3a3a06ce8432054770cddd5729c8d62866da", + "StarknetToken": "0x27dd8ce628866f1544202ae06ec57b3c9b1f775d5f7c2797de7aa1586ecf693", + "ERC20": "0x3c5ee4bc12f4247cd8071150c3f5d9bee71f40b0ef7aeae59f468d898f60933", + "kakarot": "0x3f9e4ac97c943181453ce74f1fd1c163c154c40d9cbbbe5c2453512ee1a86e6", + "UniversalLibraryCaller": "0x5e84816dcbfd11581d8d5160af5754a4adc71ab35a0c0aaa053773f61838627", + "BalanceSender": "0x2cc118f56b9d3ad311900db5254f3dca75fbf24de3b68ee670a0fb3691ac5b3" } diff --git a/deployments/starknet-sepolia/deployments.json b/deployments/starknet-sepolia/deployments.json index 4f8a8d55a..9dcc82f1f 100644 --- a/deployments/starknet-sepolia/deployments.json +++ b/deployments/starknet-sepolia/deployments.json @@ -1,7 +1,27 @@ { "kakarot": { - "address": "0x464f7e37179d2f93ea208795bdb2d0912e8257f6fb5f67ae2559251523aee19", - "tx": "0x209a134f8c8f3a9b9e98c2e4789476d4432aceeb252ecfb3dba069cec5ec974", + "address": "0x6f625bb0bd82401b268c1ba9fa0973bb9cdf732c6a6f21fe14dfd4c82a28e89", + "tx": "0x36c51e168146d9c104a4323a2dddf873a9a54d52b02f5cbc83d8cb957426f60", "artifact": "build/kakarot.json" + }, + "EVM": { + "address": "0x41c4025537b9677034f3b58f6e722e19c40a7b77a82e3851fdc6b7adf5ad414", + "tx": "0xa0b92fd60470ac35b5a64236bacbe86d6b5aaea30997aced328123cba74024", + "artifact": "build/fixtures/EVM.json" + }, + "Counter": { + "address": "0x2d6741b182475b7cfc62ec1000fbcba553ea08f2e603fa2840d0288cd2d1e3c", + "tx": "0xd1781094a55ac09c3177e6799f9d484c5477baa6d120923c942da5c2fdbfea", + "artifact": "build/fixtures/Counter.json" + }, + "MockPragmaOracle": { + "address": "0x17e64c92b06da9a331da9fd333a683a33019ae2a393254caf332d4158edc74d", + "tx": "0x3d6b91602c1e290bc65c6f85751f5ea156cf982d01c6bf1ea694d7398a9d5a5", + "artifact": "build/ssj/contracts_MockPragmaOracle" + }, + "UniversalLibraryCaller": { + "address": "0x01e12ea32baf68b1e11c1ce32595d3a61a22ccdcbc67f94c77268b6ce99fa6d4", + "tx": "0x3d6b91602c1e290bc65c6f85751f5ea156cf982d01c6bf1ea694d7398a9d5a5", + "artifact": "cairo1_contracts/utils/target/dev/library_call_UniversalLibraryCaller" } } diff --git a/kakarot_scripts/constants.py b/kakarot_scripts/constants.py index 3547a6703..c6e47822b 100644 --- a/kakarot_scripts/constants.py +++ b/kakarot_scripts/constants.py @@ -41,9 +41,9 @@ class NetworkType(Enum): "sepolia": { "name": "starknet-sepolia", "explorer_url": "https://sepolia.starkscan.co/", - "rpc_url": "https://starknet-sepolia.public.blastapi.io/rpc/v0_6", + "rpc_url": os.getenv("STARKNET_SEPOLIA_RPC_URL"), "l1_rpc_url": f"https://sepolia.infura.io/v3/{os.getenv('INFURA_KEY')}", - "type": NetworkType.PROD, + "type": NetworkType.STAGING, "chain_id": StarknetChainId.SEPOLIA, "check_interval": 5, "max_wait": 30, @@ -155,7 +155,9 @@ class NetworkType(Enum): if WEB3.is_connected(): chain_id = WEB3.eth.chain_id else: - chain_id = starknet_chain_id + chain_id = starknet_chain_id % ( + 2**53 if NETWORK["name"] != "starknet-sepolia" else 2**32 + ) except ( requests.exceptions.ConnectionError, requests.exceptions.MissingSchema, @@ -164,8 +166,13 @@ class NetworkType(Enum): logger.info( f"⚠️ Could not get chain Id from {NETWORK['rpc_url']}: {e}, defaulting to KKRT" ) - chain_id = int.from_bytes(b"KKRT", "big") starknet_chain_id = int.from_bytes(b"KKRT", "big") + chain_id = starknet_chain_id % ( + # TODO: remove once Kakarot is redeployed on sepolia + 2**53 + if NETWORK["name"] != "starknet-sepolia" + else 2**32 + ) class ChainId(IntEnum): @@ -181,56 +188,59 @@ class ChainId(IntEnum): or "0x20eB005C0b9c906691F885eca5895338E15c36De", 16, ) -SOURCE_DIR = Path("src") -SOURCE_DIR_FIXTURES = Path("tests/fixtures") -CONTRACTS = {p.stem: p for p in list(SOURCE_DIR.glob("**/*.cairo"))} -CONTRACTS_FIXTURES = {p.stem: p for p in list(SOURCE_DIR_FIXTURES.glob("**/*.cairo"))} +CAIRO_ZERO_DIR = Path("src") +CAIRO_DIR = Path("cairo1_contracts") +TESTS_DIR = Path("tests") + +CONTRACTS = { + p.stem: p + for p in ( + list(CAIRO_ZERO_DIR.glob("**/*.cairo")) + + list(TESTS_DIR.glob("**/*.cairo")) + + list(CAIRO_DIR.glob("**/*.cairo")) + ) +} BUILD_DIR = Path("build") -BUILD_DIR_FIXTURES = BUILD_DIR / "fixtures" BUILD_DIR.mkdir(exist_ok=True, parents=True) -BUILD_DIR_FIXTURES.mkdir(exist_ok=True, parents=True) BUILD_DIR_SSJ = BUILD_DIR / "ssj" DATA_DIR = Path("kakarot_scripts") / "data" -class ArtifactType(Enum): - cairo0 = 0 - cairo1 = 1 - - DEPLOYMENTS_DIR = Path("deployments") / NETWORK["name"] DEPLOYMENTS_DIR.mkdir(exist_ok=True, parents=True) COMPILED_CONTRACTS = [ - {"contract_name": "kakarot", "is_account_contract": False}, {"contract_name": "account_contract", "is_account_contract": True}, - {"contract_name": "uninitialized_account_fixture", "is_account_contract": False}, - {"contract_name": "uninitialized_account", "is_account_contract": False}, + {"contract_name": "BalanceSender", "is_account_contract": False}, + {"contract_name": "Counter", "is_account_contract": False}, + {"contract_name": "ERC20", "is_account_contract": False}, {"contract_name": "EVM", "is_account_contract": False}, + {"contract_name": "kakarot", "is_account_contract": False}, + {"contract_name": "MockPragmaOracle", "is_account_contract": False}, {"contract_name": "OpenzeppelinAccount", "is_account_contract": True}, - {"contract_name": "ERC20", "is_account_contract": False}, {"contract_name": "replace_class", "is_account_contract": False}, - {"contract_name": "Counter", "is_account_contract": False}, + {"contract_name": "StarknetToken", "is_account_contract": False}, + {"contract_name": "uninitialized_account_fixture", "is_account_contract": False}, + {"contract_name": "uninitialized_account", "is_account_contract": False}, + {"contract_name": "UniversalLibraryCaller", "is_account_contract": False}, ] DECLARED_CONTRACTS = [ - {"contract_name": "kakarot", "cairo_version": ArtifactType.cairo0}, - {"contract_name": "account_contract", "cairo_version": ArtifactType.cairo0}, - { - "contract_name": "uninitialized_account_fixture", - "cairo_version": ArtifactType.cairo0, - }, - {"contract_name": "uninitialized_account", "cairo_version": ArtifactType.cairo0}, - {"contract_name": "EVM", "cairo_version": ArtifactType.cairo0}, - {"contract_name": "OpenzeppelinAccount", "cairo_version": ArtifactType.cairo0}, - {"contract_name": "Cairo1Helpers", "cairo_version": ArtifactType.cairo1}, - {"contract_name": "Cairo1HelpersFixture", "cairo_version": ArtifactType.cairo1}, - {"contract_name": "replace_class", "cairo_version": ArtifactType.cairo0}, - {"contract_name": "Counter", "cairo_version": ArtifactType.cairo0}, - {"contract_name": "MockPragmaOracle", "cairo_version": ArtifactType.cairo1}, - {"contract_name": "StarknetToken", "cairo_version": ArtifactType.cairo1}, - {"contract_name": "ERC20", "cairo_version": ArtifactType.cairo0}, + "account_contract", + "Cairo1Helpers", + "Cairo1HelpersFixture", + "Counter", + "ERC20", + "EVM", + "kakarot", + "MockPragmaOracle", + "OpenzeppelinAccount", + "replace_class", + "StarknetToken", + "uninitialized_account_fixture", + "uninitialized_account", + "UniversalLibraryCaller", ] # PRE-EIP155 TX diff --git a/kakarot_scripts/deploy_kakarot.py b/kakarot_scripts/deploy_kakarot.py index 3fddb4f41..fc6257e0e 100644 --- a/kakarot_scripts/deploy_kakarot.py +++ b/kakarot_scripts/deploy_kakarot.py @@ -44,10 +44,7 @@ async def main(): account = await get_starknet_account() logger.info(f"ℹ️ Using account 0x{account.address:064x} as deployer") - class_hash = { - contract["contract_name"]: await declare(contract) - for contract in DECLARED_CONTRACTS - } + class_hash = {contract: await declare(contract) for contract in DECLARED_CONTRACTS} dump_declarations(class_hash) # %% Deployments @@ -88,7 +85,7 @@ async def main(): ) freshly_deployed = True - if NETWORK["type"] is NetworkType.STAGING: + if NETWORK["type"] is NetworkType.STAGING or NETWORK["type"] is NetworkType.DEV: starknet_deployments["EVM"] = await upgrade( "EVM", account.address, # owner @@ -101,21 +98,8 @@ async def main(): ) starknet_deployments["Counter"] = await upgrade("Counter") starknet_deployments["MockPragmaOracle"] = await upgrade("MockPragmaOracle") - - if NETWORK["type"] is NetworkType.DEV: - starknet_deployments["EVM"] = await deploy_starknet( - "EVM", - account.address, # owner - ETH_TOKEN_ADDRESS, # native_token_address_ - class_hash["account_contract"], # account_contract_class_hash_ - class_hash["uninitialized_account"], # uninitialized_account_class_hash_ - class_hash["Cairo1Helpers"], - COINBASE, - BLOCK_GAS_LIMIT, - ) - starknet_deployments["Counter"] = await deploy_starknet("Counter") - starknet_deployments["MockPragmaOracle"] = await deploy_starknet( - "MockPragmaOracle" + starknet_deployments["UniversalLibraryCaller"] = await upgrade( + "UniversalLibraryCaller" ) dump_deployments(starknet_deployments) @@ -124,11 +108,7 @@ async def main(): logger.info(f"ℹ️ Found default EVM address {EVM_ADDRESS}") from kakarot_scripts.utils.kakarot import get_eoa - amount = ( - 0.02 - if NETWORK["type"] is not (NetworkType.DEV or NetworkType.STAGING) - else 100 - ) + amount = 100 if NETWORK["type"] is NetworkType.DEV else 0.01 await get_eoa(amount=amount) # Set the base fee if freshly deployed @@ -152,6 +132,22 @@ async def main(): evm_deployments["CreateX"] = await deploy_with_presigned_tx( CREATEX_DEPLOYER, CREATEX_SIGNED_TX, amount=0.3, name="CreateX" ) + + if NETWORK["type"] is NetworkType.STAGING or NETWORK["type"] is NetworkType.DEV: + bridge = await deploy_evm("CairoPrecompiles", "EthStarknetBridge") + evm_deployments["Bridge"] = { + "address": int(bridge.address, 16), + "starknet_address": bridge.starknet_address, + } + await invoke( + "kakarot", + "set_authorized_cairo_precompile_caller", + int(bridge.address, 16), + 1, + ) + await invoke("kakarot", "set_coinbase", int(bridge.address, 16)) + await invoke("kakarot", "set_base_fee", 1) + dump_evm_deployments(evm_deployments) diff --git a/kakarot_scripts/utils/kakarot.py b/kakarot_scripts/utils/kakarot.py index 17794733d..98e41d740 100644 --- a/kakarot_scripts/utils/kakarot.py +++ b/kakarot_scripts/utils/kakarot.py @@ -46,9 +46,9 @@ from kakarot_scripts.utils.starknet import get_starknet_account from kakarot_scripts.utils.starknet import invoke as _invoke_starknet from kakarot_scripts.utils.starknet import wait_for_transaction +from kakarot_scripts.utils.uint256 import int_to_uint256 from tests.utils.constants import TRANSACTION_GAS_LIMIT from tests.utils.helpers import pack_calldata, rlp_encode_signed_data -from tests.utils.uint256 import int_to_uint256 logging.basicConfig() logger = logging.getLogger(__name__) @@ -316,7 +316,16 @@ def dump_deployments(deployments): def get_deployments(): try: - return json.load(open(DEPLOYMENTS_DIR / "kakarot_deployments.json", "r")) + return { + name: { + **value, + "address": int(value["address"], 16), + "starknet_address": int(value["starknet_address"], 16), + } + for name, value in json.load( + open(DEPLOYMENTS_DIR / "kakarot_deployments.json", "r") + ).items() + } except FileNotFoundError: return {} @@ -432,6 +441,7 @@ async def _wrapper(self, *args, **kwargs): data=calldata, caller_eoa=caller_eoa_ if caller_eoa_ else None, max_fee=max_fee, + gas_price=gas_price, ) if success == 0: logger.error(f"❌ {self.address}.{fun} failed") @@ -450,13 +460,12 @@ async def _wrapper(self, *args, **kwargs): async def _contract_exists(address: int) -> bool: try: await RPC_CLIENT.get_class_hash_at(address) - logger.info(f"ℹ️ Contract at address {hex(address)} already exists") return True except ClientError: return False -async def get_eoa(private_key=None, amount=10) -> Account: +async def get_eoa(private_key=None, amount=0) -> Account: private_key = private_key or keys.PrivateKey(bytes.fromhex(EVM_PRIVATE_KEY[2:])) starknet_address = await deploy_and_fund_evm_address( private_key.public_key.to_checksum_address(), amount @@ -541,6 +550,7 @@ async def eth_send_transaction( value: Union[int, str] = 0, caller_eoa: Optional[Account] = None, max_fee: Optional[int] = None, + gas_price=DEFAULT_GAS_PRICE, ): """Execute the data at the EVM contract to on Kakarot.""" evm_account = caller_eoa or await get_eoa() @@ -558,12 +568,11 @@ async def eth_send_transaction( ).nonce payload = { - "type": 0x2, + "type": 0x1, "chainId": NETWORK["chain_id"], "nonce": nonce, "gas": gas, - "maxPriorityFeePerGas": 1, - "maxFeePerGas": DEFAULT_GAS_PRICE, + "gasPrice": gas_price, "to": to_checksum_address(to) if to else None, "value": value, "data": data, diff --git a/kakarot_scripts/utils/starknet.py b/kakarot_scripts/utils/starknet.py index 4de3adc14..4dcccfb9d 100644 --- a/kakarot_scripts/utils/starknet.py +++ b/kakarot_scripts/utils/starknet.py @@ -2,7 +2,9 @@ import json import logging import random +import re import subprocess +from collections import namedtuple from copy import deepcopy from datetime import datetime from functools import cache @@ -35,16 +37,14 @@ from kakarot_scripts.constants import ( BUILD_DIR, - BUILD_DIR_FIXTURES, BUILD_DIR_SSJ, + CAIRO_DIR, + CAIRO_ZERO_DIR, CONTRACTS, - CONTRACTS_FIXTURES, DEPLOYMENTS_DIR, ETH_TOKEN_ADDRESS, NETWORK, RPC_CLIENT, - SOURCE_DIR, - ArtifactType, ChainId, NetworkType, ) @@ -58,12 +58,7 @@ # to have at least 0.1 ETH _max_fee = int(1e17) - -def int_to_uint256(value): - value = int(value) - low = value & ((1 << 128) - 1) - high = value >> 128 - return {"low": low, "high": high} +Artifact = namedtuple("Artifact", ["sierra", "casm"]) @alru_cache @@ -85,7 +80,7 @@ async def get_starknet_account( key_pair = KeyPair.from_private_key(int(private_key, 16)) public_key = None - for selector in ["get_public_key", "getPublicKey", "getSigner"]: + for selector in ["get_public_key", "getPublicKey", "getSigner", "get_owner"]: try: call = Call( to_addr=address, @@ -105,6 +100,7 @@ async def get_starknet_account( or "Invalid message selector." in message or "StarknetErrorCode.ENTRY_POINT_NOT_FOUND_IN_CONTRACT" in message or ("code 40" in message and "not found in contract" in message) + or "{'error': 'Invalid message selector'}" in message ): continue else: @@ -133,21 +129,19 @@ async def get_starknet_account( async def get_eth_contract(provider=None) -> Contract: return Contract( ETH_TOKEN_ADDRESS, - get_abi("ERC20", cairo_version=ArtifactType.cairo0), + get_abi("ERC20"), provider or await get_starknet_account(), - cairo_version=ArtifactType.cairo0, + cairo_version=0, ) @cache -def get_contract( - contract_name, address=None, provider=None, cairo_version=None -) -> Contract: +def get_contract(contract_name, address=None, provider=None) -> Contract: return Contract( address or get_deployments()[contract_name]["address"], - get_abi(contract_name, cairo_version), + get_abi(contract_name), provider or RPC_CLIENT, - cairo_version=get_artifact_version(contract_name).value, + cairo_version=get_cairo_version(contract_name), ) @@ -158,7 +152,7 @@ async def fund_address( Fund a given starknet address with {amount} ETH. """ address = int(address, 16) if isinstance(address, str) else address - amount = amount * 1e18 + amount = int(amount * 1e18) if NETWORK["name"] == "starknet-devnet": response = requests.post( "http://127.0.0.1:5050/mint", @@ -176,9 +170,7 @@ async def fund_address( raise ValueError( f"Cannot send {amount / 1e18} ETH from default account with current balance {balance / 1e18} ETH" ) - prepared = eth_contract.functions["transfer"].prepare_invoke_v1( - address, int_to_uint256(amount) - ) + prepared = eth_contract.functions["transfer"].prepare_invoke_v1(address, amount) tx = await prepared.invoke(max_fee=_max_fee) status = await wait_for_transaction(tx.hash) @@ -249,86 +241,82 @@ def get_deployments(): @cache -def get_artifact(contract_name, cairo_version=None): - if cairo_version is None: - cairo_version = get_artifact_version(contract_name) - if cairo_version == ArtifactType.cairo1: - return (BUILD_DIR_SSJ / f"contracts_{contract_name}", ArtifactType.cairo1) - - return ( - ( - BUILD_DIR / f"{contract_name}.json" - if not is_fixture_contract(contract_name) - else BUILD_DIR_FIXTURES / f"{contract_name}.json" - ), - ArtifactType.cairo0, +def get_artifact(contract_name): + artifacts = list(CAIRO_DIR.glob(f"**/*{contract_name}.*.json")) or list( + BUILD_DIR_SSJ.glob(f"**/*{contract_name}.*.json") ) + if artifacts: + sierra, casm = ( + artifacts + if "sierra.json" in artifacts[0].name + or ".contract_class.json" in artifacts[0].name + else artifacts[::-1] + ) + return Artifact(sierra=sierra, casm=casm) + + artifacts = list(BUILD_DIR.glob(f"**/*{contract_name}*.json")) + if not artifacts: + raise FileNotFoundError(f"No artifact found for {contract_name}") + return Artifact(sierra=None, casm=artifacts[0]) @cache -def get_abi(contract_name, cairo_version=None): - artifact, cairo_version = get_artifact(contract_name, cairo_version) - if cairo_version == ArtifactType.cairo1: - artifact = artifact.with_suffix(".contract_class.json") +def get_abi(contract_name): + artifact = get_artifact(contract_name) + return json.loads( + (artifact.sierra if artifact.sierra else artifact.casm).read_text() + )["abi"] - return json.loads(artifact.read_text())["abi"] +@cache +def get_cairo_version(contract_name): + return get_artifact(contract_name).sierra is not None + +@cache def get_tx_url(tx_hash: int) -> str: return f"{NETWORK['explorer_url']}/tx/0x{tx_hash:064x}" -def is_fixture_contract(contract_name): - return CONTRACTS_FIXTURES.get(contract_name) is not None - - -def get_artifact_version(contract_name): - cairo_0 = contract_name in set(CONTRACTS_FIXTURES).union(set(CONTRACTS)) - cairo_1 = any( - contract_name in str(artifact) for artifact in BUILD_DIR_SSJ.glob("*.json") - ) - if cairo_0 and cairo_1: - raise ValueError(f"Contract {contract_name} is ambiguous") - if cairo_0: - return ArtifactType.cairo0 - if cairo_1: - return ArtifactType.cairo1 - raise ValueError(f"Cannot find artifact for contract {contract_name}") - - def compile_contract(contract): logger.info(f"⏳ Compiling {contract['contract_name']}") start = datetime.now() - is_fixture = is_fixture_contract(contract["contract_name"]) - artifact, cairo_version = get_artifact(contract["contract_name"]) - - if cairo_version == ArtifactType.cairo1: - raise NotImplementedError("SSJ compilation not implemented yet") - - output = subprocess.run( - [ - "starknet-compile-deprecated", - ( - CONTRACTS[contract["contract_name"]] - if not is_fixture - else CONTRACTS_FIXTURES[contract["contract_name"]] - ), - "--output", - artifact, - "--cairo_path", - str(SOURCE_DIR), - *(["--no_debug_info"] if NETWORK["type"] is not NetworkType.DEV else []), - *(["--account_contract"] if contract["is_account_contract"] else []), - *( - ["--disable_hint_validation"] - if NETWORK["type"] is NetworkType.DEV - else [] - ), - ], - capture_output=True, + contract_path = CONTRACTS.get(contract["contract_name"]) or CONTRACTS.get( + re.sub("(?!^)([A-Z]+)", r"_\1", contract["contract_name"]).lower() ) + + if contract_path.is_relative_to(CAIRO_DIR): + output = subprocess.run( + "scarb build", shell=True, cwd=contract_path.parent, capture_output=True + ) + else: + output = subprocess.run( + [ + "starknet-compile-deprecated", + contract_path, + "--output", + BUILD_DIR / f"{contract['contract_name']}.json", + "--cairo_path", + str(CAIRO_ZERO_DIR), + *( + ["--no_debug_info"] + if NETWORK["type"] is not NetworkType.DEV + else [] + ), + *(["--account_contract"] if contract["is_account_contract"] else []), + *( + ["--disable_hint_validation"] + if NETWORK["type"] is NetworkType.DEV + else [] + ), + ], + capture_output=True, + ) + if output.returncode != 0: - raise RuntimeError(output.stderr) + raise RuntimeError( + f"❌ {contract['contract_name']} raised:\n{output.stderr}.\nOutput:\n{output.stdout}" + ) elapsed = datetime.now() - start logger.info( @@ -374,18 +362,14 @@ async def deploy_starknet_account(class_hash=None, private_key=None, amount=1): } -async def declare(contract): - logger.info(f"ℹ️ Declaring {contract['contract_name']}") - artifact, cairo_version = get_artifact(**contract) +async def declare(contract_name): + logger.info(f"ℹ️ Declaring {contract_name}") + artifact = get_artifact(contract_name) account = await get_starknet_account() - if cairo_version == ArtifactType.cairo1: - casm_compiled_contract = artifact.with_suffix( - ".compiled_contract_class.json" - ).read_text() - sierra_compiled_contract = artifact.with_suffix( - ".contract_class.json" - ).read_text() + if artifact.sierra is not None: + casm_compiled_contract = artifact.casm.read_text() + sierra_compiled_contract = artifact.sierra.read_text() casm_class = create_casm_class(casm_compiled_contract) class_hash = compute_casm_class_hash(casm_class) @@ -408,7 +392,7 @@ async def declare(contract): resp = await account.client.declare(transaction=declare_v2_transaction) else: contract_class = create_compiled_contract( - compiled_contract=artifact.read_text() + compiled_contract=artifact.casm.read_text() ) class_hash = compute_class_hash(contract_class=deepcopy(contract_class)) try: @@ -453,24 +437,14 @@ async def declare(contract): status = await wait_for_transaction(resp.transaction_hash) - logger.info( - f"{status} {contract['contract_name']} class hash: {hex(resp.class_hash)}" - ) + logger.info(f"{status} {contract_name} class hash: {hex(resp.class_hash)}") return deployed_class_hash async def deploy(contract_name, *args): logger.info(f"ℹ️ Deploying {contract_name}") - artifact, cairo_version = get_artifact(contract_name) + abi = get_abi(contract_name) declarations = get_declarations() - if cairo_version == ArtifactType.cairo0: - compiled_contract = Path(artifact).read_text() - abi = json.loads(compiled_contract)["abi"] - else: - sierra_compiled_contract = artifact.with_suffix( - ".contract_class.json" - ).read_text() - abi = json.loads(sierra_compiled_contract)["abi"] account = await get_starknet_account() deploy_result = await Contract.deploy_contract_v1( @@ -479,7 +453,7 @@ async def deploy(contract_name, *args): abi=abi, constructor_args=list(args), max_fee=_max_fee, - cairo_version=cairo_version.value, + cairo_version=get_cairo_version(contract_name), ) status = await wait_for_transaction(deploy_result.hash) logger.info( diff --git a/tests/utils/uint256.py b/kakarot_scripts/utils/uint256.py similarity index 100% rename from tests/utils/uint256.py rename to kakarot_scripts/utils/uint256.py diff --git a/solidity_contracts/src/CairoPrecompiles/EthStarknetBridge.sol b/solidity_contracts/src/CairoPrecompiles/EthStarknetBridge.sol new file mode 100644 index 000000000..bead05a28 --- /dev/null +++ b/solidity_contracts/src/CairoPrecompiles/EthStarknetBridge.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0 <0.9.0; + +import {CairoLib} from "kakarot-lib/CairoLib.sol"; + +using CairoLib for uint256; + +contract EthStarknetBridge { + /// @dev The cairo contract to call + uint256 constant starknetEth = 0x49D36570D4E46F48E99674BD3FCC84644DDD6B96F7C741B1562B82F9E004DC7; + uint256 constant TRANSFER_SELECTOR = uint256(keccak256("transfer")) % 2 ** 250; + + // State variable to store the owner of the contract + address immutable owner; + + // Constructor sets the owner of the contract + constructor() { + owner = msg.sender; + } + + // Modifier to restrict access to owner only + modifier onlyOwner() { + require(msg.sender == owner, "Not the contract owner"); + _; + } + + /// @notice Withdraws ETH from the contract to a Starknet address + function withdraw(uint256 toStarknetAddress) external onlyOwner { + uint256 balance = address(this).balance; + transfer(toStarknetAddress, balance); + } + + /// @notice Calls the Eth Cairo contract + /// @param toStarknetAddress The Starknet address to send ETH to + /// @param amount The amount of ETH to send + function transfer(uint256 toStarknetAddress, uint256 amount) public { + // Split amount in [low, high] + uint128 amountLow = uint128(amount); + uint128 amountHigh = uint128(amount >> 128); + + uint256[] memory transferCallData = new uint256[](3); + transferCallData[0] = toStarknetAddress; + transferCallData[1] = uint256(amountLow); + transferCallData[2] = uint256(amountHigh); + + starknetEth.delegatecallCairo(TRANSFER_SELECTOR, transferCallData); + } +} diff --git a/tests/end_to_end/Solmate/conftest.py b/tests/end_to_end/Solmate/conftest.py index 0e27a2a27..10d70306a 100644 --- a/tests/end_to_end/Solmate/conftest.py +++ b/tests/end_to_end/Solmate/conftest.py @@ -3,9 +3,9 @@ @pytest_asyncio.fixture(scope="module") async def from_wallet(new_eoa): - return await new_eoa() + return await new_eoa(0.1) @pytest_asyncio.fixture(scope="module") async def to_wallet(new_eoa): - return await new_eoa() + return await new_eoa(0.1) diff --git a/tests/end_to_end/Solmate/test_erc20.py b/tests/end_to_end/Solmate/test_erc20.py index 540a0010c..fb9cd56a9 100644 --- a/tests/end_to_end/Solmate/test_erc20.py +++ b/tests/end_to_end/Solmate/test_erc20.py @@ -229,36 +229,9 @@ async def test_should_permit(self, erc_20, owner, other): } ] assert await erc_20.allowance(owner.address, other.address) == TEST_SUPPLY - assert await erc_20.nonces(owner.address) == 1 + assert await erc_20.nonces(owner.address) == nonce + 1 - async def test_permit_should_fail_with_bad_nonce(self, erc_20, owner, other): - bad_nonce = 1 - deadline = 2**256 - 1 - digest = get_approval_digest( - "Kakarot Token", - erc_20.address, - { - "owner": owner.address, - "spender": other.address, - "value": MAX_INT, - }, - bad_nonce, - deadline, - ) - v, r, s = ec_sign(digest, owner.private_key) - with evm_error("INVALID_SIGNER"): - await erc_20.permit( - owner.address, - other.address, - TEST_SUPPLY, - deadline, - v, - r, - s, - caller_eoa=owner.starknet_contract, - ) - - async def test_permit_should_fail_with_bad_deadline( + async def test_should_fail_with_bad_deadline( self, erc_20, block_timestamp, owner, other ): nonce = await erc_20.nonces(owner.address) @@ -289,7 +262,7 @@ async def test_permit_should_fail_with_bad_deadline( caller_eoa=owner.starknet_contract, ) - async def test_permit_should_fail_on_replay(self, erc_20, owner, other): + async def test_should_fail_on_replay(self, erc_20, owner, other): nonce = await erc_20.nonces(owner.address) deadline = 2**256 - 1 digest = get_approval_digest( diff --git a/tests/end_to_end/conftest.py b/tests/end_to_end/conftest.py index ecbb9e8b8..b9f3df2e7 100644 --- a/tests/end_to_end/conftest.py +++ b/tests/end_to_end/conftest.py @@ -9,8 +9,11 @@ from starknet_py.net.account.account import Account from kakarot_scripts.constants import RPC_CLIENT, NetworkType +from kakarot_scripts.utils.kakarot import eth_balance_of +from kakarot_scripts.utils.kakarot import get_contract as get_solidity_contract from kakarot_scripts.utils.kakarot import get_eoa from kakarot_scripts.utils.starknet import ( + call, get_contract, get_eth_contract, get_starknet_account, @@ -48,24 +51,46 @@ def max_fee(): return int(5e17) -@pytest.fixture(scope="session") -def new_eoa(max_fee) -> Wallet: +@pytest_asyncio.fixture(scope="session") +async def new_eoa(deployer) -> Wallet: """ Return a factory to create a new EOA with enough ETH to pass ~100 tx by default. """ - async def _factory(amount=None): + deployed = [] + + async def _factory(amount=0): private_key: PrivateKey = generate_random_private_key() - return Wallet( + wallet = Wallet( address=private_key.public_key.to_checksum_address(), private_key=private_key, - starknet_contract=await get_eoa( - private_key, amount=amount or (100 * max_fee / 1e18) - ), + starknet_contract=await get_eoa(private_key, amount=amount), + ) + deployed.append(wallet) + return wallet + + yield _factory + + bridge_address = (await call("kakarot", "get_coinbase")).coinbase + bridge = await get_solidity_contract( + "CairoPrecompiles", "EthStarknetBridge", address=bridge_address + ) + gas_price = (await call("kakarot", "get_base_fee")).base_fee + gas_limit = 40_000 + tx_cost = gas_limit * gas_price + for wallet in deployed: + balance = await eth_balance_of(wallet.address) + if balance < tx_cost: + continue + + await bridge.transfer( + deployer.address, + balance - tx_cost, + caller_eoa=wallet.starknet_contract, + gas_limit=gas_limit, + gas_price=gas_price, ) - - return _factory @pytest_asyncio.fixture(scope="session") @@ -73,7 +98,7 @@ async def owner(new_eoa): """ Return the main caller of all tests. """ - return await new_eoa() + return await new_eoa(0.1) @pytest_asyncio.fixture(scope="module") @@ -81,7 +106,7 @@ async def other(new_eoa): """ Just another EOA. """ - return await new_eoa() + return await new_eoa(0.1) @pytest_asyncio.fixture(scope="session") diff --git a/tests/end_to_end/test_kakarot.py b/tests/end_to_end/test_kakarot.py index c6c099e30..d592d69ba 100644 --- a/tests/end_to_end/test_kakarot.py +++ b/tests/end_to_end/test_kakarot.py @@ -394,11 +394,11 @@ async def test_should_raise_when_tx_view_entrypoint(self, kakarot, entrypoint): class TestEthRPCEntrypoints: async def test_should_return_native_balance_of(self, new_eoa): - eoa = await new_eoa() + eoa = await new_eoa(0x1234 / 1e18) balance = ( await call("kakarot", "eth_get_balance", int(eoa.address, 16)) ).balance - assert balance == 50000000000000000000 + assert balance == 0x1234 async def test_should_return_transaction_count(self, new_eoa): eoa = await new_eoa() diff --git a/tests/src/kakarot/accounts/test_account_contract.py b/tests/src/kakarot/accounts/test_account_contract.py index d6e776596..d487e1225 100644 --- a/tests/src/kakarot/accounts/test_account_contract.py +++ b/tests/src/kakarot/accounts/test_account_contract.py @@ -15,12 +15,12 @@ ) from kakarot_scripts.constants import ARACHNID_PROXY_DEPLOYER, ARACHNID_PROXY_SIGNED_TX +from kakarot_scripts.utils.uint256 import int_to_uint256 from tests.utils.constants import CHAIN_ID, TRANSACTIONS from tests.utils.errors import cairo_error from tests.utils.helpers import generate_random_private_key, rlp_encode_signed_data from tests.utils.hints import patch_hint from tests.utils.syscall_handler import SyscallHandler -from tests.utils.uint256 import int_to_uint256 CHAIN_ID_OFFSET = 35 V_OFFSET = 27 diff --git a/tests/src/kakarot/instructions/test_environmental_information.py b/tests/src/kakarot/instructions/test_environmental_information.py index f990ac5a1..d8014db5e 100644 --- a/tests/src/kakarot/instructions/test_environmental_information.py +++ b/tests/src/kakarot/instructions/test_environmental_information.py @@ -3,8 +3,8 @@ import pytest from Crypto.Hash import keccak +from kakarot_scripts.utils.uint256 import int_to_uint256 from tests.utils.syscall_handler import SyscallHandler -from tests.utils.uint256 import int_to_uint256 EXISTING_ACCOUNT = 0xABDE1 EXISTING_ACCOUNT_SN_ADDR = 0x1234 diff --git a/tests/src/kakarot/instructions/test_stop_and_math_operations.py b/tests/src/kakarot/instructions/test_stop_and_math_operations.py index a03871da9..c052b5bcd 100644 --- a/tests/src/kakarot/instructions/test_stop_and_math_operations.py +++ b/tests/src/kakarot/instructions/test_stop_and_math_operations.py @@ -1,7 +1,7 @@ import pytest +from kakarot_scripts.utils.uint256 import int_to_uint256 from tests.utils.constants import Opcodes -from tests.utils.uint256 import int_to_uint256 class TestStopMathOperations: diff --git a/tests/src/kakarot/test_account.cairo b/tests/src/kakarot/test_account.cairo index 98d83d5df..79e753100 100644 --- a/tests/src/kakarot/test_account.cairo +++ b/tests/src/kakarot/test_account.cairo @@ -24,7 +24,7 @@ func test__init__should_return_account_with_default_dict_as_storage{ local nonce: felt; local balance_low: felt; %{ - from tests.utils.uint256 import int_to_uint256 + from kakarot_scripts.utils.uint256 import int_to_uint256 ids.evm_address = program_input["evm_address"] ids.code_len = len(program_input["code"]) @@ -68,7 +68,7 @@ func test__copy__should_return_new_account_with_same_attributes{ local nonce: felt; local balance_low: felt; %{ - from tests.utils.uint256 import int_to_uint256 + from kakarot_scripts.utils.uint256 import int_to_uint256 ids.evm_address = program_input["evm_address"] ids.code_len = len(program_input["code"]) @@ -196,7 +196,7 @@ func test__has_code_or_nonce{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, ran let (code_hash_ptr) = alloc(); local nonce: felt; %{ - from tests.utils.uint256 import int_to_uint256 + from kakarot_scripts.utils.uint256 import int_to_uint256 ids.code_len = len(program_input["code"]) segments.write_arg(ids.code, program_input["code"]) diff --git a/tests/src/kakarot/test_account.py b/tests/src/kakarot/test_account.py index b9ebdcf67..046c39d0a 100644 --- a/tests/src/kakarot/test_account.py +++ b/tests/src/kakarot/test_account.py @@ -3,8 +3,8 @@ from hypothesis import given from hypothesis.strategies import binary +from kakarot_scripts.utils.uint256 import int_to_uint256 from tests.utils.syscall_handler import SyscallHandler -from tests.utils.uint256 import int_to_uint256 class TestAccount: diff --git a/tests/src/kakarot/test_gas.cairo b/tests/src/kakarot/test_gas.cairo index 6c57612ec..80b68d8fc 100644 --- a/tests/src/kakarot/test_gas.cairo +++ b/tests/src/kakarot/test_gas.cairo @@ -58,7 +58,7 @@ func test__memory_expansion_cost_saturated{range_check_ptr}() -> felt { let (offset) = alloc(); let (size) = alloc(); %{ - from tests.utils.uint256 import int_to_uint256 + from kakarot_scripts.utils.uint256 import int_to_uint256 ids.words_len = program_input["words_len"] segments.write_arg(ids.offset, int_to_uint256(program_input["offset"])) segments.write_arg(ids.size, int_to_uint256(program_input["size"])) diff --git a/tests/src/kakarot/test_kakarot.cairo b/tests/src/kakarot/test_kakarot.cairo index a97d08208..fef315d67 100644 --- a/tests/src/kakarot/test_kakarot.cairo +++ b/tests/src/kakarot/test_kakarot.cairo @@ -37,7 +37,7 @@ func eth_call{ let (access_list) = alloc(); %{ - from tests.utils.uint256 import int_to_uint256 + from kakarot_scripts.utils.uint256 import int_to_uint256 ids.origin = program_input.get("origin", 0) ids.to.is_some = int(bool(program_input.get("to") is not None)) diff --git a/tests/src/kakarot/test_state.cairo b/tests/src/kakarot/test_state.cairo index 9b0ee80b9..97848cb73 100644 --- a/tests/src/kakarot/test_state.cairo +++ b/tests/src/kakarot/test_state.cairo @@ -113,7 +113,7 @@ func test__is_account_alive__account_alive_in_state{ let (code) = alloc(); let (code_hash_ptr) = alloc(); %{ - from tests.utils.uint256 import int_to_uint256 + from kakarot_scripts.utils.uint256 import int_to_uint256 ids.nonce = program_input["nonce"] ids.balance_low = program_input["balance_low"] diff --git a/tests/src/utils/test_bytes.py b/tests/src/utils/test_bytes.py index 2ea3b5e0a..32b8f866b 100644 --- a/tests/src/utils/test_bytes.py +++ b/tests/src/utils/test_bytes.py @@ -4,9 +4,9 @@ from hypothesis import given from hypothesis.strategies import integers +from kakarot_scripts.utils.uint256 import int_to_uint256 from tests.utils.errors import cairo_error from tests.utils.hints import patch_hint -from tests.utils.uint256 import int_to_uint256 PRIME = 0x800000000000011000000000000000000000000000000000000000000000001 diff --git a/tests/src/utils/test_uint256.py b/tests/src/utils/test_uint256.py index 30313dac2..dbdfdfa37 100644 --- a/tests/src/utils/test_uint256.py +++ b/tests/src/utils/test_uint256.py @@ -2,7 +2,7 @@ from hypothesis import given, settings from hypothesis.strategies import integers -from tests.utils.uint256 import int_to_uint256, uint256_to_int +from kakarot_scripts.utils.uint256 import int_to_uint256, uint256_to_int class TestUint256: diff --git a/tests/utils/helpers.py b/tests/utils/helpers.py index 3df45f6d8..d0fea36a9 100644 --- a/tests/utils/helpers.py +++ b/tests/utils/helpers.py @@ -13,7 +13,7 @@ from starkware.starknet.public.abi import get_storage_var_address from kakarot_scripts.constants import NETWORK -from tests.utils.uint256 import int_to_uint256 +from kakarot_scripts.utils.uint256 import int_to_uint256 PERMIT_TYPEHASH = keccak( text="Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" diff --git a/tests/utils/syscall_handler.py b/tests/utils/syscall_handler.py index 9cd505be7..fbc09f16f 100644 --- a/tests/utils/syscall_handler.py +++ b/tests/utils/syscall_handler.py @@ -16,12 +16,12 @@ get_storage_var_address, ) +from kakarot_scripts.utils.uint256 import int_to_uint256, uint256_to_int from tests.utils.constants import ( ACCOUNT_CLASS_IMPLEMENTATION, CAIRO1_HELPERS_CLASS_HASH, CHAIN_ID, ) -from tests.utils.uint256 import int_to_uint256, uint256_to_int def cairo_keccak(class_hash, calldata):