diff --git a/.env.example b/.env.example index e8d0a980a..9381ce47e 100644 --- a/.env.example +++ b/.env.example @@ -49,3 +49,6 @@ PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python WEB3_HTTP_PROVIDER_URI="http://0.0.0.0:3030" HYPOTHESIS_PROFILE=dev + +VOYAGER_API_URL= +VOYAGER_API_KEY= diff --git a/.gitignore b/.gitignore index 7d90d5288..98498cdf2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ venv deployments/* !deployments/kakarot-sepolia/ !deployments/starknet-sepolia/ +!deployments/starknet-mainnet/ !deployments/kakarot-staging/ artifacts build diff --git a/Makefile b/Makefile index d309d0aa8..0cce68d3b 100644 --- a/Makefile +++ b/Makefile @@ -77,9 +77,9 @@ run-katana: katana --chain-id test --validate-max-steps 6000000 --invoke-max-steps 14000000 --eth-gas-price 0 --strk-gas-price 0 --disable-fee --seed 0 run-anvil: - anvil --block-base-fee-per-gas 10 + anvil --block-base-fee-per-gas 1 run-nodes: @echo "Starting Anvil and Katana in messaging mode" - @anvil --block-base-fee-per-gas 10 & + @anvil --block-base-fee-per-gas 1 & @katana --chain-id test --validate-max-steps 6000000 --invoke-max-steps 14000000 --eth-gas-price 0 --strk-gas-price 0 --disable-fee --messaging .katana/messaging_config.json --seed 0 diff --git a/cairo1_contracts/utils/src/balance_sender.cairo b/cairo1_contracts/utils/src/balance_sender.cairo deleted file mode 100644 index 0afe76564..000000000 --- a/cairo1_contracts/utils/src/balance_sender.cairo +++ /dev/null @@ -1,39 +0,0 @@ -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 index a0d456d8e..9fae9055d 100644 --- a/cairo1_contracts/utils/src/lib.cairo +++ b/cairo1_contracts/utils/src/lib.cairo @@ -1,5 +1,3 @@ mod universal_library_caller; -mod balance_sender; - mod benchmark_cairo_calls; diff --git a/deployments/starknet-mainnet/declarations.json b/deployments/starknet-mainnet/declarations.json new file mode 100644 index 000000000..f73723b6b --- /dev/null +++ b/deployments/starknet-mainnet/declarations.json @@ -0,0 +1,17 @@ +{ + "account_contract": "0x40c09824ef6c8bbced0385dec75e77cecec9329c6b3abbfb113cdcef0a4b7fa", + "Cairo1Helpers": "0x28ece3751ecf5bdf2d791eb64a65bfb6a8816432b698870dba2f38a36101d58", + "Cairo1HelpersFixture": "0x4e7811d9bbba41193bd3c77d05c16f7aaa55dd1d601686b50f6fa0e3766a712", + "Counter": "0x4fc47610d8c9ce0bcfc2f9e03658f0fbcd3e0a9c351a1aa59d465a33533a7c8", + "ERC20": "0x3c5ee4bc12f4247cd8071150c3f5d9bee71f40b0ef7aeae59f468d898f60933", + "EVM": "0x7ffa7cfa35add74c4d352c3e00d14ab1594d447c1725648779972756acd4d61", + "kakarot": "0x841570a0996b9c1a5548c62b1a46d40dfb0149b8151db86f6f9ce2e7953191", + "MockPragmaOracle": "0x675f00328ff84f127d71b179b3f3a3a06ce8432054770cddd5729c8d62866da", + "OpenzeppelinAccount": "0x6153ccf69fd20f832c794df36e19135f0070d0576144f0b47f75a226e4be530", + "replace_class": "0xa187318c5e79b010cf45975f589f0a8d441fadde5b1e7ccad46501568437b5", + "StarknetToken": "0x314a6a9f01e5a28beb6a7e2e8907243469d98a7e364054657e7593bea5dcee7", + "uninitialized_account_fixture": "0x2957ff0877441dddcd140e6af50a3d45712f4f7205a36a846110a70297036be", + "uninitialized_account": "0x45f7d0803659c3f58b5b6ba46f349178253dadabbfc6ab47fa1ba4bab4699f8", + "UniversalLibraryCaller": "0x244fd35db35b48882ca2e6c2966821bd54f302b131fb22ea98e5534da390482", + "BalanceSender": "0x566fa5364a088be0e9bb7ab51a3af985245d8a0a0afbe4fd1c046af7ad8cff8" +} diff --git a/deployments/starknet-mainnet/deployments.json b/deployments/starknet-mainnet/deployments.json new file mode 100644 index 000000000..5fee1a4db --- /dev/null +++ b/deployments/starknet-mainnet/deployments.json @@ -0,0 +1,7 @@ +{ + "kakarot": { + "address": "0x6ec07c9b2724d42faa654878fafe406b860d77547e65f81d83214efc0dc4ca7", + "tx": "0xb5df9733082e46f926e02d886dceabd951f80f259ec2566f3014977b19e909", + "artifact": "None" + } +} \ No newline at end of file diff --git a/kakarot_scripts/constants.py b/kakarot_scripts/constants.py index 9af25dbc4..c2a65152e 100644 --- a/kakarot_scripts/constants.py +++ b/kakarot_scripts/constants.py @@ -20,7 +20,7 @@ load_dotenv() BLOCK_GAS_LIMIT = 7_000_000 -DEFAULT_GAS_PRICE = int(1e9) +DEFAULT_GAS_PRICE = 1 BEACON_ROOT_ADDRESS = "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" @@ -39,8 +39,9 @@ class NetworkType(Enum): "type": NetworkType.PROD, "chain_id": StarknetChainId.MAINNET, "check_interval": 1, - "max_wait": 10, + "max_wait": 60, "class_hash": 0x061DAC032F228ABEF9C6626F995015233097AE253A7F72D68552DB02F2971B8F, + "voyager_api_url": "https://api.voyager.online/beta", }, "sepolia": { "name": "starknet-sepolia", @@ -52,6 +53,7 @@ class NetworkType(Enum): "check_interval": 1, "max_wait": 10, "class_hash": 0x061DAC032F228ABEF9C6626F995015233097AE253A7F72D68552DB02F2971B8F, + "voyager_api_url": "https://sepolia-api.voyager.online/beta", }, "starknet-devnet": { "name": "starknet-devnet", @@ -265,6 +267,7 @@ class ChainId(IntEnum): ] DECLARED_CONTRACTS = [ "account_contract", + "BalanceSender", "BenchmarkCairoCalls", "Cairo1Helpers", "Cairo1HelpersFixture", diff --git a/kakarot_scripts/deploy_kakarot.py b/kakarot_scripts/deploy_kakarot.py index 2f55b2f6b..3ebffeb45 100644 --- a/kakarot_scripts/deploy_kakarot.py +++ b/kakarot_scripts/deploy_kakarot.py @@ -20,7 +20,10 @@ NetworkType, ) from kakarot_scripts.utils.kakarot import deploy as deploy_evm -from kakarot_scripts.utils.kakarot import deploy_with_presigned_tx +from kakarot_scripts.utils.kakarot import ( + deploy_and_fund_evm_address, + deploy_with_presigned_tx, +) from kakarot_scripts.utils.kakarot import dump_deployments as dump_evm_deployments from kakarot_scripts.utils.kakarot import get_deployments as get_evm_deployments from kakarot_scripts.utils.starknet import declare @@ -31,7 +34,7 @@ get_declarations, ) from kakarot_scripts.utils.starknet import get_deployments as get_starknet_deployments -from kakarot_scripts.utils.starknet import get_starknet_account, invoke, upgrade +from kakarot_scripts.utils.starknet import get_starknet_account, invoke logging.basicConfig() logger = logging.getLogger(__name__) @@ -47,12 +50,34 @@ async def main(): class_hash = {contract: await declare(contract) for contract in DECLARED_CONTRACTS} dump_declarations(class_hash) - # %% Deployments + # %% Starknet Deployments class_hash = get_declarations() starknet_deployments = get_starknet_deployments() evm_deployments = get_evm_deployments() - freshly_deployed = False + if NETWORK["type"] is not NetworkType.PROD: + 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 deploy_starknet( + "UniversalLibraryCaller" + ) + starknet_deployments["BenchmarkCairoCalls"] = await deploy_starknet( + "BenchmarkCairoCalls" + ) + + # Deploy or upgrade Kakarot if starknet_deployments.get("kakarot") and NETWORK["type"] is not NetworkType.DEV: logger.info("ℹ️ Kakarot already deployed, checking version.") deployed_class_hash = await RPC_CLIENT.get_class_hash_at( @@ -83,49 +108,16 @@ async def main(): COINBASE, BLOCK_GAS_LIMIT, ) - freshly_deployed = True - - if NETWORK["type"] is NetworkType.STAGING or NETWORK["type"] is NetworkType.DEV: - starknet_deployments["EVM"] = await upgrade( - "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 upgrade("Counter") - starknet_deployments["MockPragmaOracle"] = await upgrade("MockPragmaOracle") - starknet_deployments["UniversalLibraryCaller"] = await upgrade( - "UniversalLibraryCaller" - ) - starknet_deployments["BenchmarkCairoCalls"] = await deploy_starknet( - "BenchmarkCairoCalls" + await invoke( + "kakarot", + "set_base_fee", + DEFAULT_GAS_PRICE, + address=starknet_deployments["kakarot"]["address"], ) dump_deployments(starknet_deployments) - if EVM_ADDRESS: - logger.info(f"ℹ️ Found default EVM address {EVM_ADDRESS}") - from kakarot_scripts.utils.kakarot import get_eoa - - amount = 100 if NETWORK["type"] is NetworkType.DEV else 0.01 - await get_eoa(amount=amount) - - # Set the base fee if freshly deployed - if freshly_deployed: - await invoke("kakarot", "set_base_fee", DEFAULT_GAS_PRICE) - - # Deploy the solidity contracts - weth = await deploy_evm("WETH", "WETH9") - evm_deployments["WETH"] = { - "address": int(weth.address, 16), - "starknet_address": weth.starknet_address, - } - - # Pre-EIP155 deployments + # %% Pre-EIP155 deployments evm_deployments["Multicall3"] = await deploy_with_presigned_tx( MULTICALL3_DEPLOYER, MULTICALL3_SIGNED_TX, name="Multicall3" ) @@ -136,20 +128,32 @@ async def main(): 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) + # %% EVM Deployments + if not EVM_ADDRESS: + logger.info("ℹ️ No EVM address provided, skipping EVM deployments") + return + + logger.info(f"ℹ️ Using account {EVM_ADDRESS} as deployer") + + await deploy_and_fund_evm_address( + EVM_ADDRESS, amount=100 if NETWORK["type"] is NetworkType.DEV else 0.01 + ) + + 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)) + + weth = await deploy_evm("WETH", "WETH9") + evm_deployments["WETH"] = { + "address": int(weth.address, 16), + "starknet_address": weth.starknet_address, + } dump_evm_deployments(evm_deployments) diff --git a/kakarot_scripts/utils/kakarot.py b/kakarot_scripts/utils/kakarot.py index af7c0da7e..c1ea0b977 100644 --- a/kakarot_scripts/utils/kakarot.py +++ b/kakarot_scripts/utils/kakarot.py @@ -38,6 +38,7 @@ WEB3, ChainId, ) +from kakarot_scripts.utils.starknet import _max_fee from kakarot_scripts.utils.starknet import call as _call_starknet from kakarot_scripts.utils.starknet import fund_address as _fund_starknet_address from kakarot_scripts.utils.starknet import get_balance @@ -267,6 +268,7 @@ async def deploy( contract = await get_contract(contract_app, contract_name, caller_eoa=caller_eoa) max_fee = kwargs.pop("max_fee", None) value = kwargs.pop("value", 0) + gas_price = kwargs.pop("gas_price", DEFAULT_GAS_PRICE) receipt, response, success, _ = await eth_send_transaction( to=0, gas=int(TRANSACTION_GAS_LIMIT), @@ -274,6 +276,7 @@ async def deploy( caller_eoa=caller_eoa, max_fee=max_fee, value=value, + gas_price=gas_price, ) if success == 0: raise EvmTransactionError(bytes(response)) @@ -623,7 +626,7 @@ async def send_starknet_transaction( "execute_after": current_timestamp - 60 * 60, "execute_before": current_timestamp + 60 * 60, } - max_fee = int(5e17) if max_fee in [None, 0] else max_fee + max_fee = _max_fee if max_fee in [None, 0] else max_fee response = ( await _get_starknet_contract( "account_contract", address=evm_account.address, provider=relayer diff --git a/kakarot_scripts/utils/starknet.py b/kakarot_scripts/utils/starknet.py index fb326eed6..e1a81898b 100644 --- a/kakarot_scripts/utils/starknet.py +++ b/kakarot_scripts/utils/starknet.py @@ -57,7 +57,7 @@ # Due to some fee estimation issues, we skip it in all the calls and set instead # this hardcoded value. This has no impact apart from enforcing the signing wallet # to have at least 0.1 ETH -_max_fee = int(1e17) +_max_fee = int(0.05e18) Artifact = namedtuple("Artifact", ["sierra", "casm"]) @@ -445,6 +445,19 @@ async def declare(contract_name): async def deploy(contract_name, *args): + deployments = get_deployments() + if deployments.get(contract_name): + try: + deployed_class_hash = await RPC_CLIENT.get_class_hash_at( + deployments[contract_name]["address"] + ) + latest_class_hash = get_declarations().get(contract_name) + if latest_class_hash == deployed_class_hash: + logger.info(f"✅ {contract_name} already deployed, skipping") + return deployments[contract_name] + except ClientError: + pass + logger.info(f"ℹ️ Deploying {contract_name}") abi = get_abi(contract_name) declarations = get_declarations() @@ -469,29 +482,6 @@ async def deploy(contract_name, *args): } -async def upgrade(contract_name, *args): - deployments = get_deployments() - if not deployments.get(contract_name): - return await deploy(contract_name, *args) - - logger.info(f"ℹ️ {contract_name} already deployed, checking version.") - class_hash = get_declarations() - try: - deployed_class_hash = await RPC_CLIENT.get_class_hash_at( - deployments[contract_name]["address"] - ) - except ClientError as e: - if "Contract not found" in str(e): - logger.info(f"ℹ️ deploying {contract_name}.") - return await deploy(contract_name, *args) - - if deployed_class_hash != class_hash[contract_name]: - logger.info(f"ℹ️ redeploying {contract_name}.") - return await deploy(contract_name, *args) - - return deployments[contract_name] - - async def invoke_address(contract_address, function_name, *calldata, account=None): account = account or (await get_starknet_account()) logger.info( diff --git a/kakarot_scripts/withdraw_accounts.py b/kakarot_scripts/withdraw_accounts.py new file mode 100644 index 000000000..7fdb79513 --- /dev/null +++ b/kakarot_scripts/withdraw_accounts.py @@ -0,0 +1,87 @@ +# %% Imports +import logging +import os +from asyncio import run + +import requests + +from kakarot_scripts.constants import ETH_TOKEN_ADDRESS, NETWORK, RPC_CLIENT +from kakarot_scripts.utils.starknet import ( + get_balance, + get_declarations, + get_deployments, + invoke, +) + +logging.basicConfig() +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +# %% Fetch contract events +def get_contracts(): + contract_address = hex(get_deployments()["kakarot"]["address"]) + logger.info(f"ℹ️ Fetching contracts from {contract_address}") + url = f"{NETWORK['voyager_api_url']}/events?ps=10&p=1&contract={contract_address}" + headers = { + "accept": "application/json", + "x-api-key": os.getenv("VOYAGER_API_KEY"), + } + response = requests.get(url, headers=headers) + return [ + {item["name"]: item["value"] for item in event["dataDecoded"]} + for event in response.json()["items"] + if event.get("name") == "evm_contract_deployed" + ] + + +# %% Main +async def main(): + # %% Withdraw all accounts + + contracts = get_contracts() + + balance_prev = await get_balance(NETWORK["account_address"]) + logger.info(f"ℹ️ Current deployer balance {balance_prev / 1e18} ETH") + for contract in contracts: + balance = await get_balance(contract["starknet_contract_address"]) + if balance == 0: + logger.info( + f"ℹ️ No balance to withdraw from EVM contract {contract['evm_address']}" + ) + continue + + logger.info( + f"ℹ️ Withdrawing {balance / 1e18} ETH from EVM contract {contract['evm_address']}" + ) + current_class = await RPC_CLIENT.get_class_hash_at( + contract["starknet_contract_address"] + ) + await invoke( + "kakarot", + "upgrade_account", + int(contract["evm_contract_address"], 16), + get_declarations()["BalanceSender"], + ) + await invoke( + "BalanceSender", + "send_balance", + ETH_TOKEN_ADDRESS, + int(NETWORK["account_address"], 16), + address=int(contract["starknet_contract_address"], 16), + ) + await invoke( + "kakarot", + "upgrade_account", + int(contract["evm_contract_address"], 16), + current_class, + ) + balance = await get_balance(NETWORK["account_address"]) + logger.info( + f"ℹ️ Current deployer balance {balance / 1e18} ETH: {(balance - balance_prev) / 1e18} ETH recovered" + ) + + +# %% Run +if __name__ == "__main__": + run(main()) diff --git a/tests/fixtures/BalanceSender.cairo b/tests/fixtures/BalanceSender.cairo new file mode 100644 index 000000000..e7505322d --- /dev/null +++ b/tests/fixtures/BalanceSender.cairo @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +%lang starknet + +from openzeppelin.access.ownable.library import Ownable +from starkware.cairo.common.cairo_builtins import HashBuiltin +from starkware.starknet.common.syscalls import replace_class, get_contract_address +from kakarot.interfaces.interfaces import IERC20 + +@external +func set_implementation{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + new_implementation: felt +) { + Ownable.assert_only_owner(); + replace_class(new_implementation); + return (); +} + +@external +func send_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( + token_address: felt, recipient: felt +) { + let (this) = get_contract_address(); + let (balance) = IERC20.balanceOf(token_address, this); + IERC20.transfer(token_address, recipient, balance); + return (); +}