Skip to content

Commit

Permalink
feat: pausable (#1433)
Browse files Browse the repository at this point in the history
<!--- Please provide a general summary of your changes in the title
above -->

<!-- Give an estimate of the time you spent on this PR in terms of work
days.
Did you spend 0.5 days on this PR or rather 2 days?  -->

Time spent on this PR:

## Pull request type

<!-- Please try to limit your pull request to one type,
submit multiple pull requests if needed. -->

Please check the type of change your PR introduces:

- [ ] 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?

<!-- Please describe the current behavior that you are modifying,
or link to a relevant issue. -->

Resolves #1426

## What is the new behavior?

<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Add a Pausable feature that prevents from using public,
state-modifying entrypoint into Kakarot.
-
-

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg"
height="34" align="absmiddle"
alt="Reviewable"/>](https://reviewable.io/reviews/kkrt-labs/kakarot/1433)
<!-- Reviewable:end -->

---------

Co-authored-by: Oba <[email protected]>
  • Loading branch information
enitrat and obatirou committed Sep 23, 2024
1 parent 8fa6718 commit 73356ca
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 23 deletions.
2 changes: 2 additions & 0 deletions src/kakarot/eth_rpc.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
%lang starknet

from openzeppelin.access.ownable.library import Ownable_owner
from openzeppelin.security.pausable.library import Pausable
from starkware.cairo.common.bool import FALSE, TRUE
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.math import assert_le, assert_nn, split_felt
Expand Down Expand Up @@ -241,6 +242,7 @@ func eth_send_raw_unsigned_tx{
return_data_len: felt, return_data: felt*, success: felt, gas_used: felt
) {
alloc_locals;
Pausable.assert_not_paused();
let tx = EthTransaction.decode(tx_data_len, tx_data);

// Validate chain_id for post eip155
Expand Down
68 changes: 45 additions & 23 deletions src/kakarot/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.uint256 import Uint256
from starkware.starknet.common.syscalls import replace_class
from openzeppelin.access.ownable.library import Ownable, Ownable_owner
from openzeppelin.security.pausable.library import Pausable

// Local dependencies
from backend.starknet import Starknet
Expand All @@ -28,6 +29,47 @@ from kakarot.eth_rpc import (
eth_send_raw_unsigned_tx,
)

// / ADMIN ///

// @notice Upgrade the contract
// @dev Use the replace_hash syscall to upgrade the contract
// @param new_class_hash The new class hash
@external
func upgrade{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
new_class_hash: felt
) {
Ownable.assert_only_owner();
replace_class(new_class_hash);
kakarot_upgraded.emit(new_class_hash);
return ();
}

// @notice Transfer the ownership of the contract
// @param new_owner The new owner
@external
func transfer_ownership{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
new_owner: felt
) {
Ownable.transfer_ownership(new_owner);
return ();
}

// @notice Pause the contract
@external
func pause{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
Ownable.assert_only_owner();
Pausable._pause();
return ();
}

// @notice Unpause the contract
@external
func unpause{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
Ownable.assert_only_owner();
Pausable._unpause();
return ();
}

// Constructor
@constructor
func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
Expand All @@ -50,35 +92,12 @@ func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr
);
}

// @notive Upgrade the contract
// @dev Use the replace_hash syscall to upgrade the contract
// @param new_class_hash The new class hash
@external
func upgrade{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
new_class_hash: felt
) {
Ownable.assert_only_owner();
replace_class(new_class_hash);
kakarot_upgraded.emit(new_class_hash);
return ();
}

// @notive Returns the owner of the contract
@external
func get_owner{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (owner: felt) {
return Ownable_owner.read();
}

// @notive Transfer the ownership of the contract
// @param new_owner The new owner
@external
func transfer_ownership{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
new_owner: felt
) {
Ownable.transfer_ownership(new_owner);
return ();
}

// @notice Set the native token used by kakarot
// @dev Set the native token which will emulate the role of ETH on Ethereum
// @param native_token_address The address of the native token
Expand Down Expand Up @@ -269,6 +288,7 @@ func get_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_
func deploy_externally_owned_account{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr
}(evm_address: felt) -> (starknet_contract_address: felt) {
Pausable.assert_not_paused();
return Kakarot.deploy_externally_owned_account(evm_address);
}

Expand All @@ -279,6 +299,7 @@ func deploy_externally_owned_account{
func register_account{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
evm_address: felt
) {
Pausable.assert_not_paused();
return Kakarot.register_account(evm_address);
}

Expand Down Expand Up @@ -349,6 +370,7 @@ func handle_l1_message{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}(from_address: felt, l1_sender: felt, to_address: felt, value: felt, data_len: felt, data: felt*) {
alloc_locals;
Pausable.assert_not_paused();
let l1_messaging_contract_address = Kakarot.get_l1_messaging_contract_address();
if (from_address != l1_messaging_contract_address) {
return ();
Expand Down
50 changes: 50 additions & 0 deletions tests/src/kakarot/test_kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ from kakarot.kakarot import (
set_cairo1_helpers_class_hash,
transfer_ownership,
upgrade_account,
deploy_externally_owned_account,
handle_l1_message,
pause,
unpause,
)
from kakarot.model import model
from kakarot.account import Account
Expand Down Expand Up @@ -98,6 +102,18 @@ func compute_starknet_address{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, ra
return starknet_address;
}

func test__deploy_externally_owned_account{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr
}() {
tempvar evm_address;

%{ ids.evm_address = program_input["evm_address"] %}

deploy_externally_owned_account(evm_address=evm_address);

return ();
}

func test__register_account{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
tempvar evm_address;

Expand Down Expand Up @@ -230,3 +246,37 @@ func test__upgrade_account{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range

return ();
}

func test__handle_l1_message{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}() {
tempvar from_address;
tempvar l1_sender;
tempvar to_address;
tempvar value;
tempvar data_len;
let (data) = alloc();

%{
ids.from_address = program_input["from_address"]
ids.l1_sender = program_input["l1_sender"]
ids.to_address = program_input["to_address"]
ids.value = program_input["value"]
ids.data_len = len(program_input["data"])
segments.write_arg(ids.data, list(program_input["data"]))
%}

handle_l1_message(from_address, l1_sender, to_address, value, data_len, data);

return ();
}

func test__pause{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
pause();
return ();
}

func test__unpause{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
unpause();
return ();
}
60 changes: 60 additions & 0 deletions tests/src/kakarot/test_kakarot.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,35 @@ def _wrapper(self, *args, **kwargs):

class TestKakarot:

class TestPause:
@SyscallHandler.patch("Ownable_owner", 0xDEAD)
def test_should_assert_only_owner(self, cairo_run):
with cairo_error(message="Ownable: caller is not the owner"):
cairo_run("test__pause")

@SyscallHandler.patch("Ownable_owner", SyscallHandler.caller_address)
@SyscallHandler.patch("Pausable_paused", 0)
def test_should_pause(self, cairo_run):
cairo_run("test__pause")
SyscallHandler.mock_storage.assert_called_with(
address=get_storage_var_address("Pausable_paused"), value=1
)

class TestUnpause:

@SyscallHandler.patch("Ownable_owner", 0xDEAD)
def test_should_assert_only_owner(self, cairo_run):
with cairo_error(message="Ownable: caller is not the owner"):
cairo_run("test__unpause")

@SyscallHandler.patch("Ownable_owner", SyscallHandler.caller_address)
@SyscallHandler.patch("Pausable_paused", 1)
def test_should_unpause(self, cairo_run):
cairo_run("test__unpause")
SyscallHandler.mock_storage.assert_called_with(
address=get_storage_var_address("Pausable_paused"), value=0
)

class TestNativeToken:
@pytest.mark.slow
@SyscallHandler.patch("Ownable_owner", 0xDEAD)
Expand Down Expand Up @@ -237,7 +266,20 @@ def test_should_assert_only_owner(self, cairo_run):
with cairo_error(message="Ownable: caller is not the owner"):
cairo_run("test__set_cairo1_helpers_class_hash", class_hash=0xABC)

class TestDeployEOA:
@SyscallHandler.patch("Pausable_paused", 1)
def test_should_assert_unpaused(self, cairo_run):
with cairo_error(message="Pausable: paused"):
cairo_run(
"test__deploy_externally_owned_account", evm_address=EVM_ADDRESS
)

class TestRegisterAccount:
@SyscallHandler.patch("Pausable_paused", 1)
def test_should_assert_unpaused(self, cairo_run):
with cairo_error(message="Pausable: paused"):
cairo_run("test__register_account", evm_address=EVM_ADDRESS)

@SyscallHandler.patch("Kakarot_evm_to_starknet_address", EVM_ADDRESS, 0)
@patch(
"tests.utils.syscall_handler.SyscallHandler.caller_address",
Expand Down Expand Up @@ -322,6 +364,19 @@ def test_upgrade_account_should_replace_class(self, cairo_run):
calldata=[0x1234],
)

class TestL1Handler:
@SyscallHandler.patch("Pausable_paused", 1)
def test_should_assert_unpaused(self, cairo_run):
with cairo_error(message="Pausable: paused"):
cairo_run(
"test__handle_l1_message",
from_address=0xABC,
l1_sender=0xABC,
to_address=0xABC,
value=0xABC,
data=[],
)

class TestEthCall:
@pytest.mark.slow
@pytest.mark.SolmateERC20
Expand Down Expand Up @@ -445,6 +500,11 @@ def test_should_return_chain_id_modulo_53(self, cairo_run, chain_id):
assert res == chain_id % 2**53

class TestEthSendRawTransactionEntrypoint:
@SyscallHandler.patch("Pausable_paused", 1)
def test_should_assert_unpaused(self, cairo_run):
with cairo_error(message="Pausable: paused"):
cairo_run("test__eth_send_raw_unsigned_tx", tx_data_len=0, tx_data=[])

def test_should_raise_invalid_chain_id_tx_type_different_from_0(
self, cairo_run
):
Expand Down

0 comments on commit 73356ca

Please sign in to comment.