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

tests: e2e eip 3074 tests #1129

Merged
merged 2 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ artifacts
build

#foundry
/cache
/out
out
cache
/broadcast

.DS_Store
Expand All @@ -33,3 +33,5 @@ logs

tests/ef_tests/test_data
!tests/ef_tests/test_data/.gitkeep

bin/
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ check-resources:
build-sol:
git submodule update --init --recursive
forge build --names --force
$(MAKE) build-sol-experimental

build-sol-experimental:
docker run --rm \
-v $$(pwd):/app/foundry \
-u $$(id -u):$$(id -g) \
ghcr.io/paradigmxyz/foundry-alphanet@sha256:64ac81c19b910e766ce750499a2c9de064dce4fa9c4fc1e42368fdd73fc48dde \
--foundry-directory /app/foundry/experimental_contracts \
--foundry-command build



install-katana:
cargo install --git https://github.com/dojoengine/dojo --locked --tag "${KATANA_VERSION}" katana
Expand Down
9 changes: 9 additions & 0 deletions experimental_contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[profile.default]
src = 'src'
test = 'tests'
out = '../solidity_contracts/build'
libs = ['../lib']

[rpc_endpoints]
anvil = "http://127.0.0.1:8545"
kakarot = "http://127.0.0.1:3030"
53 changes: 53 additions & 0 deletions experimental_contracts/src/EIP3074/BaseAuth.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

/// @title BaseAuth
/// @author Anna Carroll <https://github.com/anna-carroll/3074>
abstract contract BaseAuth {
/// @notice magic byte to disambiguate EIP-3074 signature payloads
uint8 constant MAGIC = 0x04;

/// @notice produce a digest for the authorizer to sign
/// @param commit - any 32-byte value used to commit to transaction validity conditions
/// @param nonce - signer's current nonce
/// @return digest - sign the `digest` to authorize the invoker to execute the `calls`
/// @dev signing `digest` authorizes this contact to execute code on behalf of the signer
/// the logic of the inheriting contract should encode rules which respect the information within `commit`
/// @dev the authorizer includes `commit` in their signature to ensure the authorized contract will only execute intended actions(s).
/// the Invoker logic MUST implement constraints on the contract execution based on information in the `commit`;
/// otherwise, any EOA that signs an AUTH for the Invoker will be compromised
/// @dev per EIP-3074, digest = keccak256(MAGIC || paddedChainId || paddedNonce || paddedInvokerAddress || commit)
function getDigest(bytes32 commit, uint256 nonce) public view returns (bytes32 digest) {
digest =
keccak256(abi.encodePacked(MAGIC, bytes32(block.chainid), bytes32(nonce), bytes32(uint256(uint160(address(this)))), commit));
}

function authSimple(address authority, bytes32 commit, uint8 v, bytes32 r, bytes32 s)
internal
pure
returns (bool success)
{
bytes memory authArgs = abi.encodePacked(yParity(v), r, s, commit);
assembly {
success := auth(authority, add(authArgs, 0x20), mload(authArgs))
}
}

function authCallSimple(address to, bytes memory data, uint256 value, uint256 gasLimit)
internal
returns (bool success)
{
assembly {
success := authcall(gasLimit, to, value, add(data, 0x20), mload(data), 0, 0)
}
}

/// @dev Internal helper to convert `v` to `yParity` for `AUTH`
function yParity(uint8 v) private pure returns (uint8 yParity_) {
assembly {
switch lt(v, 35)
case true { yParity_ := eq(v, 28) }
default { yParity_ := mod(sub(v, 35), 2) }
}
}
}
35 changes: 35 additions & 0 deletions experimental_contracts/src/EIP3074/GasSponsorInvoker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.23;

import { BaseAuth } from "./BaseAuth.sol";

/// @title Gas Sponsor Invoker
/// @notice Invoker contract using EIP-3074 to sponsor gas for authorized transactions
contract GasSponsorInvoker is BaseAuth {
/// @notice Executes a call authorized by an external account (EOA)
/// @param authority The address of the authorizing external account
/// @param commit A 32-byte value committing to transaction validity conditions
/// @param v The recovery byte of the signature
/// @param r Half of the ECDSA signature pair
/// @param s Half of the ECDSA signature pair
/// @param to The target contract address to call
/// @param data The data payload for the call
/// @return success True if the call was successful
function sponsorCall(
address authority,
bytes32 commit,
uint8 v,
bytes32 r,
bytes32 s,
address to,
bytes calldata data
) external returns (bool success) {
// Ensure the transaction is authorized by the signer
require(authSimple(authority, commit, v, r, s), "Authorization failed");

// Execute the call as authorized by the signer
success = authCallSimple(to, data, 0, 0);
require(success, "Call execution failed");
}
}
15 changes: 15 additions & 0 deletions experimental_contracts/src/EIP3074/SenderRecorder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.23;

contract SenderRecorder {
address public lastSender;

function recordSender() external {
lastSender = msg.sender;
}

function reset() external {
lastSender = address(0);
}
}
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ markers = [
"SolmateERC721",
"UniswapV2ERC20",
"UniswapV2Factory",
"EIP3074",
"AccountContract",
"Utils",
"Safe",
Expand Down
12 changes: 10 additions & 2 deletions src/kakarot/instructions/system_operations.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ from starkware.cairo.common.cairo_secp.ec_point import EcPoint
from starkware.cairo.common.cairo_secp.bigint import uint256_to_bigint
from starkware.cairo.common.cairo_secp.bigint3 import BigInt3
from starkware.cairo.common.registers import get_fp_and_pc
from starkware.cairo.common.uint256 import Uint256, uint256_lt, uint256_le
from starkware.cairo.common.uint256 import Uint256, uint256_lt, uint256_le, uint256_eq
from starkware.cairo.common.default_dict import default_dict_new
from starkware.cairo.common.dict_access import DictAccess

Expand Down Expand Up @@ -1035,7 +1035,15 @@ namespace SystemOperations {
tempvar extra_gas = access_gas_cost + create_gas_cost + transfer_gas_cost;
let evm = EVM.charge_gas(evm, extra_gas + memory_expansion.cost);

let gas = Gas.compute_message_call_gas(gas_param, evm.gas_left);
let (is_gas_zero) = uint256_eq(gas_param, Uint256(0, 0));
if (is_gas_zero != FALSE) {
let (high, low) = split_felt(evm.gas_left);
tempvar gas_limit = Uint256(low, high);
} else {
tempvar gas_limit = gas_param;
}
let gas_limit = Uint256([ap - 2], [ap - 1]);
let gas = Gas.compute_message_call_gas(gas_limit, evm.gas_left);

// Charge the fixed message call gas
let evm = EVM.charge_gas(evm, gas);
Expand Down
19 changes: 19 additions & 0 deletions tests/end_to_end/EIP3074/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pytest_asyncio


@pytest_asyncio.fixture(scope="package")
async def gas_sponsor_invoker(deploy_contract, owner):
return await deploy_contract(
"EIP3074",
"GasSponsorInvoker",
caller_eoa=owner.starknet_contract,
)


@pytest_asyncio.fixture(scope="package")
async def sender_recorder(deploy_contract, owner):
return await deploy_contract(
"EIP3074",
"SenderRecorder",
caller_eoa=owner.starknet_contract,
)
39 changes: 39 additions & 0 deletions tests/end_to_end/EIP3074/test_eip_3074.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import pytest
from eth_utils import keccak

from tests.utils.helpers import ec_sign


@pytest.fixture(scope="module")
def commit():
return keccak(b"Some unique commit data")


@pytest.fixture(autouse=True)
async def cleanup(sender_recorder):
yield
await sender_recorder.reset()


@pytest.mark.asyncio(scope="package")
@pytest.mark.EIP3074
class TestEIP3074:
class TestEIP3074Integration:
async def test_should_execute_authorized_call(
self, gas_sponsor_invoker, sender_recorder, other, commit
):
initial_sender = await sender_recorder.lastSender()
assert int(initial_sender, 16) == 0
signer_nonce = await other.starknet_contract.get_nonce()
digest = await gas_sponsor_invoker.getDigest(commit, signer_nonce)
v, r_, s_ = ec_sign(digest, other.private_key)

calldata = sender_recorder.get_function_by_name(
"recordSender"
)()._encode_transaction_data()

await gas_sponsor_invoker.sponsorCall(
other.address, commit, v, r_, s_, sender_recorder.address, calldata
)
last_sender = await sender_recorder.lastSender()
assert last_sender == other.address
16 changes: 8 additions & 8 deletions tests/end_to_end/PlainOpcodes/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,26 @@


@pytest_asyncio.fixture(scope="package")
async def counter(deploy_solidity_contract, owner):
return await deploy_solidity_contract(
async def counter(deploy_contract, owner):
return await deploy_contract(
"PlainOpcodes",
"Counter",
caller_eoa=owner.starknet_contract,
)


@pytest_asyncio.fixture(scope="package")
async def caller(deploy_solidity_contract, owner):
return await deploy_solidity_contract(
async def caller(deploy_contract, owner):
return await deploy_contract(
"PlainOpcodes",
"Caller",
caller_eoa=owner.starknet_contract,
)


@pytest_asyncio.fixture(scope="package")
async def plain_opcodes(deploy_solidity_contract, counter, owner):
return await deploy_solidity_contract(
async def plain_opcodes(deploy_contract, counter, owner):
return await deploy_contract(
"PlainOpcodes",
"PlainOpcodes",
counter.address,
Expand All @@ -30,8 +30,8 @@ async def plain_opcodes(deploy_solidity_contract, counter, owner):


@pytest_asyncio.fixture(scope="package")
async def revert_on_fallbacks(deploy_solidity_contract, owner):
return await deploy_solidity_contract(
async def revert_on_fallbacks(deploy_contract, owner):
return await deploy_contract(
"PlainOpcodes",
"ContractRevertOnFallbackAndReceive",
caller_eoa=owner.starknet_contract,
Expand Down
12 changes: 4 additions & 8 deletions tests/end_to_end/PlainOpcodes/test_counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
@pytest.mark.Counter
class TestCounter:
class TestCount:
async def test_should_return_0_after_deployment(
self, deploy_solidity_contract, owner
):
counter = await deploy_solidity_contract(
async def test_should_return_0_after_deployment(self, deploy_contract, owner):
counter = await deploy_contract(
"PlainOpcodes",
"Counter",
caller_eoa=owner.starknet_contract,
Expand Down Expand Up @@ -64,11 +62,9 @@ async def test_should_set_count_to_0(self, counter):
assert await counter.count() == 0

class TestDeploymentWithValue:
async def test_deployment_with_value_should_fail(
self, deploy_solidity_contract
):
async def test_deployment_with_value_should_fail(self, deploy_contract):
with evm_error():
await deploy_solidity_contract("PlainOpcodes", "Counter", value=1)
await deploy_contract("PlainOpcodes", "Counter", value=1)

class TestLoops:
@pytest.mark.parametrize("iterations", [0, 50, 100])
Expand Down
10 changes: 4 additions & 6 deletions tests/end_to_end/PlainOpcodes/test_safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@


@pytest_asyncio.fixture(scope="package")
async def safe(deploy_solidity_contract, owner):
return await deploy_solidity_contract(
async def safe(deploy_contract, owner):
return await deploy_contract(
"PlainOpcodes", "Safe", caller_eoa=owner.starknet_contract
)

Expand Down Expand Up @@ -58,10 +58,8 @@ async def test_should_withdraw_call_eth(self, safe, owner, eth_balance_of):
assert owner_balance_after - owner_balance_before + gas_used == safe_balance

class TestDeploySafeWithValue:
async def test_deploy_safe_with_value(
self, safe, deploy_solidity_contract, owner
):
safe = await deploy_solidity_contract(
async def test_deploy_safe_with_value(self, safe, deploy_contract, owner):
safe = await deploy_contract(
"PlainOpcodes",
"Safe",
caller_eoa=owner.starknet_contract,
Expand Down
4 changes: 2 additions & 2 deletions tests/end_to_end/Solmate/test_erc20.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@


@pytest_asyncio.fixture(scope="module")
async def erc_20(deploy_solidity_contract, owner):
return await deploy_solidity_contract(
async def erc_20(deploy_contract, owner):
return await deploy_contract(
"Solmate",
"ERC20",
"Kakarot Token",
Expand Down
Loading
Loading