From fa62f107c9c2fcd2df121c3bd3a968b2e73986ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Tue, 27 Aug 2024 12:35:59 +0200 Subject: [PATCH] Add cairo1 utils --- 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 | 6 ++- deployments/starknet-sepolia/deployments.json | 5 +++ kakarot_scripts/constants.py | 1 + kakarot_scripts/deploy_kakarot.py | 8 ++-- kakarot_scripts/utils/starknet.py | 7 +++- .../CairoPrecompiles/EthStarknetBridge.sol | 2 - tests/end_to_end/Solmate/test_erc20.py | 33 ++-------------- tests/end_to_end/conftest.py | 3 +- 15 files changed, 114 insertions(+), 41 deletions(-) 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 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..8cb270f10 --- /dev/null +++ b/cairo1_contracts/utils/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "library_call" +version = "0.1.0" diff --git a/cairo1_contracts/utils/Scarb.toml b/cairo1_contracts/utils/Scarb.toml new file mode 100644 index 000000000..f559ff841 --- /dev/null +++ b/cairo1_contracts/utils/Scarb.toml @@ -0,0 +1,12 @@ +[package] +name = "library_call" +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 9774fe9f5..c4c401168 100644 --- a/deployments/starknet-sepolia/declarations.json +++ b/deployments/starknet-sepolia/declarations.json @@ -11,5 +11,7 @@ "MockPragmaOracle": "0x675f00328ff84f127d71b179b3f3a3a06ce8432054770cddd5729c8d62866da", "StarknetToken": "0x27dd8ce628866f1544202ae06ec57b3c9b1f775d5f7c2797de7aa1586ecf693", "ERC20": "0x3c5ee4bc12f4247cd8071150c3f5d9bee71f40b0ef7aeae59f468d898f60933", - "kakarot": "0x3f9e4ac97c943181453ce74f1fd1c163c154c40d9cbbbe5c2453512ee1a86e6" -} \ No newline at end of file + "kakarot": "0x3f9e4ac97c943181453ce74f1fd1c163c154c40d9cbbbe5c2453512ee1a86e6", + "UniversalLibraryCaller": "0x5e84816dcbfd11581d8d5160af5754a4adc71ab35a0c0aaa053773f61838627", + "BalanceSender": "0x2cc118f56b9d3ad311900db5254f3dca75fbf24de3b68ee670a0fb3691ac5b3" +} diff --git a/deployments/starknet-sepolia/deployments.json b/deployments/starknet-sepolia/deployments.json index 8db7aaedd..9dcc82f1f 100644 --- a/deployments/starknet-sepolia/deployments.json +++ b/deployments/starknet-sepolia/deployments.json @@ -18,5 +18,10 @@ "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 1b743bf13..d4524bacc 100644 --- a/kakarot_scripts/constants.py +++ b/kakarot_scripts/constants.py @@ -231,6 +231,7 @@ class ArtifactType(Enum): {"contract_name": "StarknetToken", "cairo_version": ArtifactType.cairo1}, {"contract_name": "ERC20", "cairo_version": ArtifactType.cairo0}, {"contract_name": "kakarot", "cairo_version": ArtifactType.cairo0}, + {"contract_name": "UniversalLibraryCaller", "cairo_version": ArtifactType.cairo1}, ] # PRE-EIP155 TX diff --git a/kakarot_scripts/deploy_kakarot.py b/kakarot_scripts/deploy_kakarot.py index 2e80e594a..219b4ae0c 100644 --- a/kakarot_scripts/deploy_kakarot.py +++ b/kakarot_scripts/deploy_kakarot.py @@ -155,6 +155,10 @@ async def main(): if NETWORK["type"] is (NetworkType.DEV or NetworkType.STAGING): 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", @@ -162,10 +166,6 @@ async def main(): 1, ) await invoke("kakarot", "set_coinbase", int(bridge.address, 16)) - evm_deployments["bridge"] = { - "address": int(bridge.address, 16), - "starknet_address": bridge.starknet_address, - } await invoke("kakarot", "set_base_fee", 1) dump_evm_deployments(evm_deployments) diff --git a/kakarot_scripts/utils/starknet.py b/kakarot_scripts/utils/starknet.py index d72c5fa65..df529dd5a 100644 --- a/kakarot_scripts/utils/starknet.py +++ b/kakarot_scripts/utils/starknet.py @@ -254,6 +254,9 @@ 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: + if artifacts := list(Path("cairo1_contracts").glob(f"**/*{contract_name}*")): + return artifacts[0].with_suffix("").with_suffix(""), ArtifactType.cairo1 + return (BUILD_DIR_SSJ / f"contracts_{contract_name}", ArtifactType.cairo1) return ( @@ -286,7 +289,9 @@ def is_fixture_contract(contract_name): 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") + contract_name in str(artifact) + for artifact in list(BUILD_DIR_SSJ.glob("*.json")) + + list(Path("cairo1_contracts").glob("**/*.json")) ) if cairo_0 and cairo_1: raise ValueError(f"Contract {contract_name} is ambiguous") diff --git a/solidity_contracts/src/CairoPrecompiles/EthStarknetBridge.sol b/solidity_contracts/src/CairoPrecompiles/EthStarknetBridge.sol index 1571544aa..a6b640b4c 100644 --- a/solidity_contracts/src/CairoPrecompiles/EthStarknetBridge.sol +++ b/solidity_contracts/src/CairoPrecompiles/EthStarknetBridge.sol @@ -43,8 +43,6 @@ contract EthStarknetBridge { transferCallData[1] = uint256(amountLow); transferCallData[2] = uint256(amountHigh); - // TODO: fine tune the 100_000 gas limit - require(gasleft() > 100_000, "Not enough gas to call Eth Cairo contract"); starknetEth.delegatecallCairo(TRANSFER_SELECTOR, transferCallData); } } 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 2f416e85d..b9f3df2e7 100644 --- a/tests/end_to_end/conftest.py +++ b/tests/end_to_end/conftest.py @@ -77,8 +77,7 @@ async def _factory(amount=0): "CairoPrecompiles", "EthStarknetBridge", address=bridge_address ) gas_price = (await call("kakarot", "get_base_fee")).base_fee - # Hard coded gas limit according to the require(gasleft > 100k) in the bridge contract - gas_limit = 150000 + gas_limit = 40_000 tx_cost = gas_limit * gas_price for wallet in deployed: balance = await eth_balance_of(wallet.address)