Skip to content

Commit 04b4bcd

Browse files
authored
fix: credit origin chain ID on SignedFills (#93)
1 parent 035f80a commit 04b4bcd

File tree

4 files changed

+56
-29
lines changed

4 files changed

+56
-29
lines changed

crates/rpc/examples/filler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ where
160160
// produce an UnsignedFill from the AggregateOrder
161161
let mut unsigned_fill = UnsignedFill::from(&agg);
162162
// populate the Order contract addresses for each chain
163-
for chain_id in agg.destination_chain_ids() {
163+
for chain_id in agg.target_chain_ids() {
164164
unsigned_fill = unsigned_fill.with_chain(
165165
chain_id,
166166
self.constants

crates/types/src/agg/order.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,18 @@ impl AggregateOrders {
9797
}
9898
}
9999

100-
/// Get the unique destination chain ids for the aggregated outputs.
101-
pub fn destination_chain_ids(&self) -> Vec<u64> {
100+
/// Get the unique target chain ids for the aggregated outputs.
101+
pub fn target_chain_ids(&self) -> Vec<u64> {
102102
HashSet::<u64>::from_iter(self.outputs.keys().map(|(chain_id, _)| *chain_id))
103103
.into_iter()
104104
.collect()
105105
}
106106

107-
/// Get the aggregated Outputs for a given chain id.
108-
pub fn outputs_for(&self, target_chain_id: u64) -> Vec<RollupOrders::Output> {
107+
/// Get the aggregated Outputs for a given target chain id.
108+
/// # Warning ⚠️
109+
/// All Orders in the AggregateOrders MUST have originated on the same rollup.
110+
/// Otherwise, the aggregated Outputs will be incorrectly credited.
111+
pub fn outputs_for(&self, target_chain_id: u64, ru_chain_id: u64) -> Vec<RollupOrders::Output> {
109112
let mut o = Vec::new();
110113
for ((chain_id, token), recipient_map) in &self.outputs {
111114
if *chain_id == target_chain_id {
@@ -114,7 +117,7 @@ impl AggregateOrders {
114117
token: *token,
115118
amount: U256::from(*amount),
116119
recipient: *recipient,
117-
chainId: *chain_id as u32,
120+
chainId: ru_chain_id as u32,
118121
});
119122
}
120123
}

crates/types/src/signing/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ pub enum SigningError {
2222
"Target chain id is missing. Populate it by calling with_chain before attempting to sign"
2323
)]
2424
MissingChainId,
25+
/// Missing rollup chain id for a Fill.
26+
#[error(
27+
"Rollup chain id is missing. Populate it by calling with_ru_chain_id before attempting to sign"
28+
)]
29+
MissingRollupChainId,
2530
/// Missing chain config for a specific chain.
2631
#[error("Target Order contract address is missing for chain id {0}. Populate it by calling with_chain before attempting to sign")]
2732
MissingOrderContract(u64),

crates/types/src/signing/fill.rs

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,17 @@ impl From<&SignedFill> for FillPermit2 {
112112
/// An UnsignedFill is a helper type used to easily transform an AggregateOrder into a single SignedFill with correct permit2 semantics.
113113
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
114114
pub struct UnsignedFill<'a> {
115+
/// The rollup chain id from which the Orders originated.
116+
ru_chain_id: Option<u64>,
117+
/// The set of Orders to fill. Multiple Orders can be aggregated into a single Fill,
118+
/// but they MUST all originate on the same rollup chain indicated by `ru_chain_id`.
115119
orders: Cow<'a, AggregateOrders>,
120+
/// The deadline for the Fill, after which it cannot be mined.
116121
deadline: Option<u64>,
122+
/// The Permit2 nonce for the Fill, used to prevent replay attacks.
117123
nonce: Option<u64>,
118-
destination_chains: HashMap<u64, Address>,
124+
/// The (chain_id, order_contract_address) for each target chain on which Fills will be submitted.
125+
target_chains: HashMap<u64, Address>,
119126
}
120127

121128
impl<'a> From<&'a AggregateOrders> for UnsignedFill<'a> {
@@ -128,10 +135,11 @@ impl<'a> UnsignedFill<'a> {
128135
/// Get a new UnsignedFill from a set of AggregateOrders.
129136
pub fn new(orders: &'a AggregateOrders) -> Self {
130137
Self {
138+
ru_chain_id: None,
131139
orders: orders.into(),
132140
deadline: None,
133141
nonce: None,
134-
destination_chains: HashMap::new(),
142+
target_chains: HashMap::new(),
135143
}
136144
}
137145

@@ -145,38 +153,46 @@ impl<'a> UnsignedFill<'a> {
145153
Self { deadline: Some(deadline), ..self }
146154
}
147155

148-
/// Add the chain id and Order contract address to the UnsignedOrder.
156+
/// Add the rollup chain id to the UnsignedFill.
157+
/// This is the rollup chain id from which the Orders originated,
158+
/// to which the Fill should be credited.
159+
/// MUST call this before signing, cannot be inferred.
160+
pub fn with_ru_chain_id(self, ru_chain_id: u64) -> Self {
161+
Self { ru_chain_id: Some(ru_chain_id), ..self }
162+
}
163+
164+
/// Add the chain id and Order contract address to the UnsignedFill.
149165
pub fn with_chain(mut self, chain_id: u64, order_contract_address: Address) -> Self {
150-
self.destination_chains.insert(chain_id, order_contract_address);
166+
self.target_chains.insert(chain_id, order_contract_address);
151167
self
152168
}
153169

154-
/// Sign the UnsignedFill, generating a SignedFill for each destination chain.
170+
/// Sign the UnsignedFill, generating a SignedFill for each target chain.
155171
/// Use if Filling Orders with the same signing key on every chain.
156172
pub async fn sign<S: Signer>(
157173
&self,
158174
signer: &S,
159175
) -> Result<HashMap<u64, SignedFill>, SigningError> {
160176
let mut fills = HashMap::new();
161177

162-
// loop through each destination chain and sign the fills
163-
for destination_chain_id in self.orders.destination_chain_ids() {
164-
let signed_fill = self.sign_for(destination_chain_id, signer).await?;
165-
fills.insert(destination_chain_id, signed_fill);
178+
// loop through each target chain and sign the fills
179+
for target_chain_id in self.orders.target_chain_ids() {
180+
let signed_fill = self.sign_for(target_chain_id, signer).await?;
181+
fills.insert(target_chain_id, signed_fill);
166182
}
167183

168184
// return the fills
169185
Ok(fills)
170186
}
171187

172-
/// Sign the UnsignedFill for a specific destination chain.
173-
/// Use if Filling Orders with different signing keys on respective destination chains.
188+
/// Sign the UnsignedFill for a specific target chain.
189+
/// Use if Filling Orders with different signing keys on respective target chains.
174190
/// # Warning ⚠️
175-
/// *All* Outputs MUST be filled on all destination chains, else the Order Inputs will not be transferred.
176-
/// Take care when using this function to produce SignedFills for every destination chain.
191+
/// *All* Outputs MUST be filled on all target chains, else the Order Inputs will not be transferred on the rollup.
192+
/// Take care when using this function to produce SignedFills for every target chain.
177193
pub async fn sign_for<S: Signer>(
178194
&self,
179-
chain_id: u64,
195+
target_chain_id: u64,
180196
signer: &S,
181197
) -> Result<SignedFill, SigningError> {
182198
let now = Utc::now();
@@ -185,14 +201,17 @@ impl<'a> UnsignedFill<'a> {
185201
// if deadline is None, populate it as now + 12 seconds (can only mine within the current block)
186202
let deadline = self.deadline.unwrap_or(now.timestamp() as u64 + 12);
187203

188-
// get the destination order address
189-
let destination_order_address = self
190-
.destination_chains
191-
.get(&chain_id)
192-
.ok_or(SigningError::MissingOrderContract(chain_id))?;
204+
// get the target order address
205+
let target_order_address = self
206+
.target_chains
207+
.get(&target_chain_id)
208+
.ok_or(SigningError::MissingOrderContract(target_chain_id))?;
209+
210+
// get the rollup chain id, or throw an error if not set
211+
let ru_chain_id = self.ru_chain_id.ok_or(SigningError::MissingRollupChainId)?;
193212

194-
// get the outputs for the chain from the AggregateOrders
195-
let outputs = self.orders.outputs_for(chain_id);
213+
// get the outputs for the target chain from the AggregateOrders
214+
let outputs = self.orders.outputs_for(target_chain_id, ru_chain_id);
196215
// generate the permitted tokens from the Outputs
197216
let permitted: Vec<TokenPermissions> = outputs.iter().map(Into::into).collect();
198217

@@ -202,8 +221,8 @@ impl<'a> UnsignedFill<'a> {
202221
permitted,
203222
deadline,
204223
nonce,
205-
chain_id,
206-
*destination_order_address,
224+
target_chain_id,
225+
*target_order_address,
207226
);
208227

209228
// sign it

0 commit comments

Comments
 (0)