Skip to content
This repository has been archived by the owner on Jan 9, 2025. It is now read-only.

Commit

Permalink
Make kakarot upgradable (#969)
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 #<Issue number>

## What is the new behavior?

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

-
-
-

<!-- 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/969)
<!-- Reviewable:end -->
  • Loading branch information
ClementWalter authored Feb 13, 2024
1 parent 37907b3 commit febd374
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 4 deletions.
2 changes: 2 additions & 0 deletions scripts/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ class ArtifactType(Enum):
{"contract_name": "EVM", "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},
]
DECLARED_CONTRACTS = [
{"contract_name": "kakarot", "cairo_version": ArtifactType.cairo0},
Expand All @@ -158,6 +159,7 @@ class ArtifactType(Enum):
{"contract_name": "EVM", "cairo_version": ArtifactType.cairo0},
{"contract_name": "OpenzeppelinAccount", "cairo_version": ArtifactType.cairo0},
{"contract_name": "Precompiles", "cairo_version": ArtifactType.cairo1},
{"contract_name": "replace_class", "cairo_version": ArtifactType.cairo0},
]

EVM_PRIVATE_KEY = os.getenv("EVM_PRIVATE_KEY")
Expand Down
5 changes: 3 additions & 2 deletions scripts/utils/starknet.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async def get_starknet_account(
raise ValueError(
"address was not given in arg nor in env variable, see README.md#Deploy"
)
address = int(address, 16)
address = int(address, 16) if isinstance(address, str) else address
private_key = private_key or NETWORK["private_key"]
if private_key is None:
raise ValueError(
Expand Down Expand Up @@ -348,7 +348,7 @@ def _convert_offset_to_hex(obj):
)


async def deploy_starknet_account(class_hash, private_key=None, amount=1):
async def deploy_starknet_account(class_hash=None, private_key=None, amount=1):
salt = random.randint(0, 2**251)
private_key = private_key or NETWORK["private_key"]
if private_key is None:
Expand All @@ -357,6 +357,7 @@ async def deploy_starknet_account(class_hash, private_key=None, amount=1):
)
key_pair = KeyPair.from_private_key(int(private_key, 16))
constructor_calldata = [key_pair.public_key]
class_hash = class_hash or get_declarations()["OpenzeppelinAccount"]
address = compute_address(
salt=salt,
class_hash=class_hash,
Expand Down
4 changes: 4 additions & 0 deletions src/kakarot/events.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
@event
func evm_contract_deployed(evm_contract_address: felt, starknet_contract_address: felt) {
}

@event
func kakarot_upgraded(new_class_hash: felt) {
}
19 changes: 17 additions & 2 deletions src/kakarot/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ from starkware.cairo.common.bool import FALSE, TRUE
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.math_cmp import is_not_zero
from starkware.cairo.common.uint256 import Uint256
from starkware.starknet.common.syscalls import get_caller_address
from starkware.starknet.common.syscalls import get_caller_address, replace_class
from starkware.cairo.common.registers import get_fp_and_pc
from openzeppelin.access.ownable.library import Ownable

// Local dependencies
from backend.starknet import Starknet
from kakarot.account import Account
from kakarot.model import model
from kakarot.events import kakarot_upgraded
from kakarot.library import Kakarot
from kakarot.model import model
from utils.utils import Helpers

// Constructor
Expand All @@ -38,6 +40,19 @@ 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 ();
}

// @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
20 changes: 20 additions & 0 deletions tests/end_to_end/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,23 @@ def eth_get_code():
from scripts.utils.kakarot import eth_get_code

return eth_get_code


@pytest.fixture
def call():
"""
Send a Starknet call.
"""
from scripts.utils.starknet import call

return call


@pytest.fixture
def invoke():
"""
Send a Starknet transaction.
"""
from scripts.utils.starknet import invoke

return invoke
49 changes: 49 additions & 0 deletions tests/end_to_end/test_kakarot.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ def evm(get_contract):
return get_contract("EVM")


@pytest.fixture(scope="session")
async def other():
"""
Just another Starknet contract.
"""
from scripts.utils.starknet import deploy_starknet_account, get_starknet_account

account_info = await deploy_starknet_account()
return await get_starknet_account(account_info["address"])


@pytest.fixture(scope="session")
async def class_hashes():
"""
All declared class hashes.
"""
from scripts.utils.starknet import get_declarations

return get_declarations()


@pytest_asyncio.fixture(scope="session")
async def origin(evm: Contract, addresses):
"""
Expand Down Expand Up @@ -184,3 +205,31 @@ async def test_eth_call_should_succeed(
assert result.success == 1
assert result.return_data == []
assert result.gas_used == 21_000

class TestUpgrade:

async def test_should_raise_when_caller_is_not_owner(
self, starknet, kakarot, invoke, other, class_hashes
):
prev_class_hash = await starknet.get_class_hash_at(kakarot.address)
await invoke("kakarot", "upgrade", class_hashes["EVM"], account=other)
new_class_hash = await starknet.get_class_hash_at(kakarot.address)
assert prev_class_hash == new_class_hash

async def test_should_raise_when_class_hash_is_not_declared(
self, starknet, kakarot, invoke
):
prev_class_hash = await starknet.get_class_hash_at(kakarot.address)
await invoke("kakarot", "upgrade", 0xDEAD)
new_class_hash = await starknet.get_class_hash_at(kakarot.address)
assert prev_class_hash == new_class_hash

async def test_should_upgrade_class_hash(
self, starknet, kakarot, invoke, class_hashes
):
prev_class_hash = await starknet.get_class_hash_at(kakarot.address)
await invoke("kakarot", "upgrade", class_hashes["replace_class"])
new_class_hash = await starknet.get_class_hash_at(kakarot.address)
assert prev_class_hash != new_class_hash
assert new_class_hash == class_hashes["replace_class"]
await invoke("kakarot", "upgrade", prev_class_hash)
12 changes: 12 additions & 0 deletions tests/fixtures/replace_class.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
%lang starknet

from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.starknet.common.syscalls import replace_class

@external
func upgrade{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
new_class_hash: felt
) {
replace_class(new_class_hash);
return ();
}

0 comments on commit febd374

Please sign in to comment.