Skip to content

Commit

Permalink
Feat/infinite allowance (kkrt-labs#508)
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: 1d

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

Currently Kakarot cannot transfer ETH on behalf of the EOAs or contract
accounts.

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

Resolves kkrt-labs#464 

## What is the new behavior?

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

- EOA and contract_account give infinite allowance to Kakarot

## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
  • Loading branch information
TotalPizza authored Feb 20, 2023
1 parent efb551e commit f519876
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 32 deletions.
7 changes: 6 additions & 1 deletion src/kakarot/accounts/contract/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
%lang starknet

// Starkware dependencies
from kakarot.interfaces.interfaces import IEth, IKakarot
from openzeppelin.access.ownable.library import Ownable
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.bool import FALSE
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.math import unsigned_div_rem
from starkware.cairo.common.registers import get_label_location
from starkware.cairo.common.uint256 import Uint256
from starkware.cairo.common.uint256 import Uint256, uint256_not

// @title ContractAccount main library file.
// @notice This file contains the EVM smart contract account representation logic.
Expand Down Expand Up @@ -55,6 +56,10 @@ namespace ContractAccount {
is_initialized_.write(1);
Ownable.initializer(kakarot_address);
evm_address.write(_evm_address);
// Give infinite ETH transfer allowance to Kakarot
let (native_token_address) = IKakarot.get_native_token(kakarot_address);
let (infinite) = uint256_not(Uint256(0, 0));
IEth.approve(native_token_address, kakarot_address, infinite);
return ();
}

Expand Down
13 changes: 9 additions & 4 deletions src/kakarot/accounts/eoa/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

from utils.utils import Helpers
from kakarot.constants import Constants
from kakarot.interfaces.interfaces import IEth, IKakarot
from starkware.cairo.common.cairo_builtins import HashBuiltin, SignatureBuiltin, BitwiseBuiltin
from starkware.cairo.common.cairo_secp.signature import verify_eth_signature_uint256
from starkware.starknet.common.syscalls import get_tx_info, get_caller_address, call_contract
from starkware.cairo.common.uint256 import Uint256
from starkware.cairo.common.uint256 import Uint256, uint256_not
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.math import split_felt
from starkware.cairo.common.cairo_keccak.keccak import keccak, finalize_keccak, keccak_bigend
Expand Down Expand Up @@ -63,6 +64,10 @@ namespace ExternallyOwnedAccount {
assert is_initialized = 0;
evm_address.write(_evm_address);
kakarot_address.write(_kakarot_address);
// Give infinite ETH transfer allowance to Kakarot
let (native_token_address) = IKakarot.get_native_token(_kakarot_address);
let (infinite) = uint256_not(Uint256(0, 0));
IEth.approve(native_token_address, _kakarot_address, infinite);
is_initialized_.write(1);
return ();
}
Expand Down Expand Up @@ -135,8 +140,8 @@ namespace ExternallyOwnedAccount {
assert [current_tx_calldata] = payload_len;
assert offset = 1;
assert selector = DEPLOY_CONTRACT_ACCOUNT;
// Else run the bytecode of the destination contract
}else{
// Else run the bytecode of the destination contract
} else {
// execute_at_address signature is
// address: felt, value: felt, gas_limit: felt, calldata_len: felt, calldata: felt*
assert [current_tx_calldata] = destination;
Expand All @@ -146,7 +151,7 @@ namespace ExternallyOwnedAccount {
assert offset = 4;
assert selector = EXECUTE_AT_ADDRESS_SELECTOR;
}
memcpy(current_tx_calldata + offset, payload, payload_len);
memcpy(current_tx_calldata + offset, payload, payload_len);
let res = call_contract(
contract_address=_kakarot_address,
function_selector=selector,
Expand Down
4 changes: 2 additions & 2 deletions src/kakarot/accounts/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.bool import FALSE
from starkware.starknet.common.syscalls import deploy as deploy_syscall
from starkware.starknet.common.syscalls import get_caller_address, get_contract_address
from starkware.starknet.common.syscalls import get_contract_address
from starkware.starknet.common.storage import normalize_address
from starkware.cairo.common.hash_state import (
hash_finalize,
Expand Down Expand Up @@ -93,7 +93,7 @@ namespace Accounts {
assert constructor_calldata[0] = kakarot_address;
assert constructor_calldata[1] = evm_address;
IAccount.initialize(account_address, class_hash, 2, constructor_calldata);
evm_contract_deployed.emit(evm_address,account_address);
evm_contract_deployed.emit(evm_address, account_address);
return (account_address=account_address);
}
}
6 changes: 6 additions & 0 deletions src/kakarot/interfaces/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ namespace IEth {

func transfer(recipient: felt, amount: Uint256) -> (success: felt) {
}

func approve(spender: felt, amount: Uint256) -> (success: felt) {
}
}

@contract_interface
Expand Down Expand Up @@ -66,6 +69,9 @@ namespace IKakarot {
func set_native_token(native_token_address_: felt) {
}

func get_native_token() -> (native_token_address: felt) {
}

func deploy(bytecode_len: felt, bytecode: felt*) -> (
evm_contract_address: felt, starknet_contract_address: felt
) {
Expand Down
10 changes: 10 additions & 0 deletions src/kakarot/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,16 @@ func set_native_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_chec
return Kakarot.set_native_token(native_token_address_);
}

// @notice Get the native token address
// @dev Return the address used to emulate the role of ETH on Ethereum
// @return native_token_address The address of the native token
@view
func get_native_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
native_token_address: felt
) {
return Kakarot.get_native_token();
}

// @notice Deploy a new contract account and execute constructor
// @param bytes_len: the constructor + contract bytecode lenght
// @param bytes: the constructor + contract bytecode
Expand Down
12 changes: 11 additions & 1 deletion src/kakarot/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.bool import FALSE
from starkware.starknet.common.syscalls import deploy as deploy_syscall
from starkware.starknet.common.syscalls import get_caller_address, get_contract_address
from starkware.starknet.common.syscalls import get_caller_address
// OpenZeppelin dependencies
from openzeppelin.access.ownable.library import Ownable

Expand Down Expand Up @@ -161,6 +161,16 @@ namespace Kakarot {
return ();
}

// @notice Get the native token address
// @dev Return the address used to emulate the role of ETH on Ethereum
// @return native_token_address The address of the native token
func get_native_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
native_token_address: felt
) {
let (native_token_address_) = native_token_address.read();
return (native_token_address_,);
}

// @notice deploy contract account
// @dev First deploy a contract_account with no bytecode, then run the calldata as bytecode with the new address,
// then set the bytecode with the result of the initial run
Expand Down
4 changes: 3 additions & 1 deletion src/kakarot/precompiles/modexp.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ namespace PrecompileModExpUint256 {
bytes_len = bytes_len_low;
}

let (gas_cost) = ModExpHelpersUint256.calculate_mod_exp_gas(b_size, m_size, e_size, b, e, m);
let (gas_cost) = ModExpHelpersUint256.calculate_mod_exp_gas(
b_size, m_size, e_size, b, e, m
);

return (output_len=bytes_len, output=bytes, gas_used=gas_cost);
}
Expand Down
13 changes: 1 addition & 12 deletions tests/fixtures/2_kakarot.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,6 @@ async def contract_account_class(starknet: Starknet) -> DeclaredClass:
)


@pytest_asyncio.fixture(scope="session")
async def contract_account(starknet: Starknet):
contract = await starknet.deploy(
source="./src/kakarot/accounts/contract/contract_account.cairo",
cairo_path=["src"],
disable_hint_validation=True,
)
await contract.initialize(1, 1).execute(caller_address=1)
return contract


@pytest_asyncio.fixture(scope="session")
async def externally_owned_account_class(starknet: Starknet):
return await starknet.declare(
Expand All @@ -49,7 +38,7 @@ async def account_proxy_class(starknet: Starknet):
)


@pytest_asyncio.fixture(scope="package")
@pytest_asyncio.fixture(scope="session")
async def kakarot(
starknet: Starknet,
eth: StarknetContract,
Expand Down
8 changes: 8 additions & 0 deletions tests/fixtures/ERC20.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ func allowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
// Externals
//

// Unsecure mint function
@external
func mint{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
to: felt, amount: Uint256
) {
return ERC20._mint(to, amount);
}

@external
func transfer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
recipient: felt, amount: Uint256
Expand Down
5 changes: 1 addition & 4 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@
from starkware.starknet.testing.contract import StarknetContract
from web3 import Web3

from tests.utils.helpers import (
generate_random_private_key,
hex_string_to_bytes_array,
)
from tests.integration.helpers.wrap_kakarot import get_contract, wrap_for_kakarot
from tests.utils.helpers import generate_random_private_key, hex_string_to_bytes_array
from tests.utils.reporting import traceit

logger = logging.getLogger()
Expand Down
74 changes: 72 additions & 2 deletions tests/unit/src/kakarot/accounts/contract/test_contract_account.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,92 @@
import random

import pytest
import pytest_asyncio
from starkware.starknet.testing.contract import StarknetContract
from starkware.starknet.testing.starknet import Starknet

from tests.utils.errors import kakarot_error
from tests.utils.reporting import traceit

random.seed(0)


@pytest_asyncio.fixture(scope="module")
async def contract_account(starknet: Starknet, kakarot: StarknetContract):
contract = await starknet.deploy(
source="./src/kakarot/accounts/contract/contract_account.cairo",
cairo_path=["src"],
disable_hint_validation=True,
)
await contract.initialize(kakarot.contract_address, 1).execute(
caller_address=kakarot.contract_address
)
return contract


@pytest.mark.asyncio
class TestContractAccount:
@pytest.mark.parametrize("bytecode_len", [0, 15, 16, 17, 30, 31, 32, 33])
async def test_should_store_code(
self, contract_account: StarknetContract, bytecode_len
self, contract_account: StarknetContract, bytecode_len, kakarot
):
bytecode = [random.randint(0, 255) for _ in range(bytecode_len)]

with traceit.context("contract_account"):
await contract_account.write_bytecode(bytecode).execute(caller_address=1)
await contract_account.write_bytecode(bytecode).execute(
caller_address=kakarot.contract_address
)
stored_bytecode = (await contract_account.bytecode().call()).result.bytecode
assert stored_bytecode == bytecode

class TestInitialize:
async def test_should_run_only_once(
self, contract_account: StarknetContract, kakarot
):
with kakarot_error():
await contract_account.initialize(kakarot.contract_address, 1).execute(
caller_address=kakarot.contract_address
)

async def test_should_set_ownership(
self, contract_account: StarknetContract, kakarot
):
with kakarot_error():
await contract_account.write_bytecode([0]).execute(caller_address=1)
await contract_account.write_bytecode([0]).execute(
caller_address=kakarot.contract_address
)

async def test_should_give_inifinite_allowance_to_kakarot(
self, contract_account: StarknetContract, kakarot, eth
):
# Check that current allowance is MAX Uint256
assert (
str(
(
await eth.allowance(
contract_account.contract_address,
kakarot.contract_address,
).call()
).result.remaining
)
== "Uint256(low=340282366920938463463374607431768211455, high=340282366920938463463374607431768211455)"
)
# Test whether this actually results in having infinite allowance
await eth.mint(contract_account.contract_address, (1000, 0)).execute(
caller_address=2
)
await eth.transferFrom(
contract_account.contract_address, 1, (1000, 0)
).execute(caller_address=kakarot.contract_address)
assert (
str(
(
await eth.allowance(
contract_account.contract_address,
kakarot.contract_address,
).call()
).result.remaining
)
== "Uint256(low=340282366920938463463374607431768211455, high=340282366920938463463374607431768211455)"
)
4 changes: 1 addition & 3 deletions tests/unit/src/kakarot/accounts/eoa/mock_kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ func execute_at_address{
@external
func deploy_contract_account{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}(bytecode_len: felt, bytecode: felt*) -> (
bytecode_len: felt, bytecode: felt*
) {
}(bytecode_len: felt, bytecode: felt*) -> (bytecode_len: felt, bytecode: felt*) {
return (bytecode_len, bytecode);
}
1 change: 0 additions & 1 deletion tests/unit/src/kakarot/accounts/eoa/test_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ async def test_execute_should_make_all_calls_and_return_concat_results(
), # calldata
]


assert (
await externally_owned_account.test__execute__should_make_all_calls_and_return_concat_results(
calls, list(calldata)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ from starkware.cairo.common.uint256 import Uint256, assert_uint256_eq
from starkware.cairo.common.math import split_felt
from starkware.starknet.common.syscalls import get_contract_address

// Third party dependencies
from openzeppelin.token.erc20.library import ERC20

// Local dependencies
from utils.utils import Helpers
from kakarot.model import model
from kakarot.interfaces.interfaces import IKakarot, IContractAccount
from kakarot.stack import Stack
from kakarot.memory import Memory
from kakarot.constants import Constants, contract_account_class_hash, account_proxy_class_hash
from kakarot.constants import (
Constants,
contract_account_class_hash,
account_proxy_class_hash,
native_token_address,
)
from kakarot.execution_context import ExecutionContext
from kakarot.instructions.memory_operations import MemoryOperations
from kakarot.instructions.environmental_information import EnvironmentalInformation
Expand All @@ -31,9 +39,29 @@ func constructor{
}(contract_account_class_hash_: felt, account_proxy_class_hash_) {
account_proxy_class_hash.write(account_proxy_class_hash_);
contract_account_class_hash.write(contract_account_class_hash_);
let (contract_address: felt) = get_contract_address();
native_token_address.write(contract_address);
return ();
}

// @dev The contract account initilization includes a call to the Kakarot contract
// in order to get the native token address. As the Kakarot contract is not deployed within this test, we make a call to this contract instead.
@view
func get_native_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (
native_token_address: felt
) {
return Kakarot.get_native_token();
}

// @dev The contract account initilization includes a call to an ERC20 contract to set an infitite transfer allowance to Kakarot.
// As the ERC20 contract is not deployed within this test, we make a call to this contract instead.
@external
func approve{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
spender: felt, amount: Uint256
) -> (success: felt) {
return ERC20.approve(spender, amount);
}

func init_context{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}() -> model.ExecutionContext* {
Expand Down
Loading

0 comments on commit f519876

Please sign in to comment.