Skip to content
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
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ alloy-hardforks = "0.4.7"
alloy-network = { version = "1.8.2", default-features = false }
alloy-primitives = { version = "1.5.7", default-features = false }
alloy-provider = { version = "1.8.2", default-features = false }
alloy-rlp = { version = "0.3.13", default-features = false }
alloy-rlp = { version = "0.3.15", default-features = false }
alloy-rpc-types-engine = "1.8.2"
alloy-rpc-types-eth = { version = "1.8.2" }
alloy-serde = { version = "1.8.2", default-features = false }
Expand Down
113 changes: 89 additions & 24 deletions crates/primitives/src/transaction/key_authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ impl SelectorRule {
/// - `limits`: `None` (omitted or 0x80) = unlimited spending, `Some([])` = no spending, `Some([...])` = specific limits
/// - `allowed_calls`: `None` (canonically omitted, explicit 0x80 accepted) = unrestricted,
/// `Some([])` = scoped with no allowed calls, `Some([...])` = scoped calls
#[derive(Clone, Debug, PartialEq, Eq, Hash, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)]
#[rlp(trailing)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(test, reth_codecs::add_arbitrary_tests(rlp))]
Expand All @@ -119,6 +118,8 @@ pub struct KeyAuthorization {
/// Unix timestamp when key expires.
/// - `None` (RLP 0x80) = key never expires (stored as u64::MAX in precompile)
/// - `Some(timestamp)` = key expires at this timestamp
///
/// Note: Some(0) will get decoded as None after RLP roundtrip.
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
pub expiry: Option<u64>,

Expand Down Expand Up @@ -235,7 +236,6 @@ pub struct KeyAuthorizationChainIdError {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[rlp(trailing)]
#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
pub struct SignedKeyAuthorization {
/// Key authorization for provisioning access keys
Expand Down Expand Up @@ -276,36 +276,42 @@ impl<'a> arbitrary::Arbitrary<'a> for KeyAuthorization {
}

mod rlp {
use super::TokenLimit;
use alloy_primitives::{Address, U256};
use super::*;
use alloc::borrow::Cow;
use alloy_rlp::{Decodable, Encodable};
use core::num::NonZeroU64;

#[derive(
Clone, Debug, PartialEq, Eq, Hash, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable,
)]
#[rlp(trailing)]
#[rlp(trailing(canonical))]
struct TokenLimitWire {
token: Address,
limit: U256,
period: Option<u64>,
period: Option<NonZeroU64>,
}

impl From<TokenLimitWire> for TokenLimit {
fn from(value: TokenLimitWire) -> Self {
impl From<&TokenLimit> for TokenLimitWire {
fn from(value: &TokenLimit) -> Self {
let TokenLimit {
token,
limit,
period,
} = value;
Self {
token: value.token,
limit: value.limit,
period: value.period.unwrap_or(0),
token: *token,
limit: *limit,
period: NonZeroU64::new(*period),
}
}
}

impl From<&TokenLimit> for TokenLimitWire {
fn from(value: &TokenLimit) -> Self {
impl From<TokenLimitWire> for TokenLimit {
fn from(value: TokenLimitWire) -> Self {
Self {
token: value.token,
limit: value.limit,
period: (value.period != 0).then_some(value.period),
period: value.period.map(|period| period.get()).unwrap_or(0),
}
}
}
Expand All @@ -325,6 +331,72 @@ mod rlp {
TokenLimitWire::from(self).length()
}
}

#[derive(alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)]
#[rlp(trailing(canonical))]
#[expect(clippy::owned_cow)]
struct KeyAuthorizationWire<'a> {
chain_id: u64,
key_type: SignatureType,
key_id: Address,
expiry: Option<NonZeroU64>,
limits: Option<Cow<'a, Vec<TokenLimit>>>,
allowed_calls: Option<Cow<'a, Vec<CallScope>>>,
}

impl<'a> From<&'a KeyAuthorization> for KeyAuthorizationWire<'a> {
fn from(value: &'a KeyAuthorization) -> Self {
let KeyAuthorization {
chain_id,
key_type,
key_id,
expiry,
limits,
allowed_calls,
} = value;
Self {
chain_id: *chain_id,
key_type: *key_type,
key_id: *key_id,
expiry: expiry.and_then(NonZeroU64::new),
limits: limits.as_ref().map(Cow::Borrowed),
allowed_calls: allowed_calls.as_ref().map(Cow::Borrowed),
}
}
}

impl<'a> From<KeyAuthorizationWire<'a>> for KeyAuthorization {
fn from(value: KeyAuthorizationWire<'a>) -> Self {
Self {
chain_id: value.chain_id,
key_type: value.key_type,
key_id: value.key_id,
expiry: value.expiry.map(|expiry| expiry.get()),
limits: value
.limits
.map(|limits| limits.into_owned().into_iter().collect()),
allowed_calls: value
.allowed_calls
.map(|allowed_calls| allowed_calls.into_owned().into_iter().collect()),
}
}
}

impl Encodable for KeyAuthorization {
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
KeyAuthorizationWire::from(self).encode(out)
}

fn length(&self) -> usize {
KeyAuthorizationWire::from(self).length()
}
}

impl Decodable for KeyAuthorization {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(KeyAuthorizationWire::decode(buf)?.into())
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -504,17 +576,15 @@ mod tests {
fn test_token_limit_decode_accepts_explicit_zero_period_field() {
let token = Address::random();
let limit = U256::from(42);
let period = 0u64;

let mut encoded = Vec::new();
alloy_rlp::Header {
list: true,
payload_length: token.length() + limit.length() + period.length(),
payload_length: token.length() + limit.length(),
}
.encode(&mut encoded);
token.encode(&mut encoded);
limit.encode(&mut encoded);
period.encode(&mut encoded);

let decoded: TokenLimit =
<TokenLimit as Decodable>::decode(&mut encoded.as_slice()).expect("decode token limit");
Expand Down Expand Up @@ -736,11 +806,6 @@ mod tests {
chain_id.encode(&mut payload);
key_type.encode(&mut payload);
key_id.encode(&mut payload);
payload.extend_from_slice(&[
alloy_rlp::EMPTY_STRING_CODE,
alloy_rlp::EMPTY_STRING_CODE,
alloy_rlp::EMPTY_STRING_CODE,
]);

let mut encoded = Vec::new();
alloy_rlp::Header {
Expand All @@ -761,7 +826,7 @@ mod tests {

let mut reencoded = Vec::new();
decoded.encode(&mut reencoded);
assert!(reencoded.len() < encoded.len());
assert_eq!(reencoded.len(), encoded.len());
}

#[test]
Expand Down
Loading