Skip to content

Commit b8e7efe

Browse files
authored
fix(vapp): charge signer for withdrawing for prover (#154)
1 parent 839b5cf commit b8e7efe

2 files changed

Lines changed: 208 additions & 17 deletions

File tree

crates/vapp/src/state.rs

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -541,25 +541,57 @@ impl<A: Storage<Address, Account>, R: Storage<RequestId, bool>> VAppState<A, R>
541541
let auctioneer = Address::try_from(body.auctioneer.as_slice())
542542
.map_err(|_| VAppPanic::AddressDeserializationFailed)?;
543543

544-
// Validate that the account has sufficient balance for withdrawal + auctioneer fee.
545-
debug!("validate account has sufficient balance");
546-
let balance = self.accounts.entry(account)?.or_default().get_balance();
547-
let total_amount = u256::add(amount, auctioneer_fee)?;
548-
if balance < total_amount {
549-
return Err(VAppPanic::InsufficientBalance {
550-
account,
551-
amount: total_amount,
552-
balance,
553-
});
554-
}
544+
if account == from {
545+
// Self withdraw (normal user or prover owner).
546+
debug!("validate balance for self withdraw (account pays amount + fee)");
547+
let balance = self.accounts.entry(account)?.or_default().get_balance();
548+
let total = u256::add(amount, auctioneer_fee)?;
549+
if balance < total {
550+
return Err(VAppPanic::InsufficientBalance {
551+
account,
552+
amount: total,
553+
balance,
554+
});
555+
}
555556

556-
// Deduct the amount from the account.
557-
debug!("deduct amount from account");
558-
self.accounts.entry(account)?.or_default().deduct_balance(amount)?;
557+
// Deduct the amount from the withdrawing account.
558+
info!("├── Account({}): - {} $PROVE", account, amount);
559+
self.accounts.entry(account)?.or_default().deduct_balance(amount)?;
560+
// Deduct the fee from the withdrawing account.
561+
info!("├── Account({}): - {} $PROVE (fee)", account, auctioneer_fee);
562+
self.accounts.entry(account)?.or_default().deduct_balance(auctioneer_fee)?;
563+
} else {
564+
// Someone else withdrawing for a prover.
565+
debug!("validate balances for prover withdraw (prover pays amount, signer pays fee)");
566+
567+
let prover_balance = self.accounts.entry(account)?.or_default().get_balance();
568+
if prover_balance < amount {
569+
return Err(VAppPanic::InsufficientBalance {
570+
account,
571+
amount,
572+
balance: prover_balance,
573+
});
574+
}
559575

560-
// Deduct and transfer the auctioneer fee.
561-
debug!("deduct and transfer auctioneer fee");
562-
self.accounts.entry(account)?.or_default().deduct_balance(auctioneer_fee)?;
576+
let from_balance = self.accounts.entry(from)?.or_default().get_balance();
577+
if from_balance < auctioneer_fee {
578+
return Err(VAppPanic::InsufficientBalance {
579+
account: from,
580+
amount: auctioneer_fee,
581+
balance: from_balance,
582+
});
583+
}
584+
585+
// Deduct the amount from the prover.
586+
info!("├── Account({}): - {} $PROVE", account, amount);
587+
self.accounts.entry(account)?.or_default().deduct_balance(amount)?;
588+
// Deduct the fee from the signer.
589+
info!("├── Account({}): - {} $PROVE (fee)", from, auctioneer_fee);
590+
self.accounts.entry(from)?.or_default().deduct_balance(auctioneer_fee)?;
591+
}
592+
593+
// Credit the fee to the auctioneer.
594+
info!("└── Auctioneer({}): + {} $PROVE (fee)", auctioneer, auctioneer_fee);
563595
self.accounts.entry(auctioneer)?.or_default().add_balance(auctioneer_fee)?;
564596

565597
// Return the withdraw action.

crates/vapp/tests/withdraw.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,162 @@ fn test_withdraw_insufficient_for_auctioneer_fee() {
145145

146146
assert!(matches!(result, Err(VAppPanic::InsufficientBalance { .. })));
147147
}
148+
149+
#[test]
150+
fn test_withdraw_prover_self_withdraw() {
151+
let mut test = setup();
152+
let prover_address = test.fulfiller.address();
153+
let prover_owner = test.requester.address();
154+
let auctioneer = test.state.auctioneer;
155+
156+
// Create a prover with owner different from prover address.
157+
let create_tx = create_prover_tx(prover_address, prover_owner, U256::from(500), 0, 1, 1);
158+
test.state.execute::<MockVerifier>(&create_tx).unwrap();
159+
160+
// Give the prover account some balance.
161+
let initial_balance = U256::from(200) * U256::from(10).pow(U256::from(18));
162+
let prover_deposit = deposit_tx(prover_address, initial_balance, 0, 2, 2);
163+
test.state.execute::<MockVerifier>(&prover_deposit).unwrap();
164+
assert_account_balance(&mut test, prover_address, initial_balance);
165+
166+
// Give the prover owner balance to pay the withdraw fee.
167+
let owner_balance = U256::from(10) * U256::from(10).pow(U256::from(18));
168+
let owner_deposit = deposit_tx(prover_owner, owner_balance, 0, 3, 3);
169+
test.state.execute::<MockVerifier>(&owner_deposit).unwrap();
170+
171+
// Prover owner withdraws from prover account (prover pays amount, owner pays fee).
172+
let withdraw_amount = U256::from(100) * U256::from(10).pow(U256::from(18));
173+
let withdraw_tx = withdraw_tx(&test.requester, prover_address, withdraw_amount, 0);
174+
let receipt = test.state.execute::<MockVerifier>(&withdraw_tx).unwrap();
175+
176+
// Verify amount was deducted from prover, fee from owner.
177+
let expected_prover_balance = U256::from(100) * U256::from(10).pow(U256::from(18)); // 200 - 100 = 100
178+
let expected_owner_balance = U256::from(9) * U256::from(10).pow(U256::from(18)); // 10 - 1 = 9
179+
let auctioneer_fee = U256::from(10).pow(U256::from(18)); // 1 PROVE
180+
assert_account_balance(&mut test, prover_address, expected_prover_balance);
181+
assert_account_balance(&mut test, prover_owner, expected_owner_balance);
182+
assert_account_balance(&mut test, auctioneer, auctioneer_fee);
183+
assert_withdraw_receipt(&receipt, prover_address, withdraw_amount);
184+
}
185+
186+
#[test]
187+
fn test_withdraw_third_party_for_prover() {
188+
let mut test = setup();
189+
let prover_address = test.fulfiller.address();
190+
let prover_owner = test.requester.address();
191+
let third_party = test.signers[0].clone(); // Third party who will sign the withdraw
192+
let auctioneer = test.state.auctioneer;
193+
194+
// Create a prover with owner different from prover address.
195+
let create_tx = create_prover_tx(prover_address, prover_owner, U256::from(500), 0, 1, 1);
196+
test.state.execute::<MockVerifier>(&create_tx).unwrap();
197+
198+
// Give the prover account balance for withdrawal.
199+
let prover_balance = U256::from(150) * U256::from(10).pow(U256::from(18));
200+
let prover_deposit = deposit_tx(prover_address, prover_balance, 0, 2, 2);
201+
test.state.execute::<MockVerifier>(&prover_deposit).unwrap();
202+
203+
// Give the third party balance for fee payment.
204+
let third_party_balance = U256::from(10) * U256::from(10).pow(U256::from(18));
205+
let third_party_deposit = deposit_tx(third_party.address(), third_party_balance, 0, 3, 3);
206+
test.state.execute::<MockVerifier>(&third_party_deposit).unwrap();
207+
208+
// Third party withdraws from prover account (account != signer).
209+
let withdraw_amount = U256::from(100) * U256::from(10).pow(U256::from(18));
210+
let withdraw_tx = withdraw_tx(&third_party, prover_address, withdraw_amount, 0);
211+
let receipt = test.state.execute::<MockVerifier>(&withdraw_tx).unwrap();
212+
213+
// Verify amount was deducted from prover, fee from third party.
214+
let expected_prover_balance = U256::from(50) * U256::from(10).pow(U256::from(18)); // 150 - 100 = 50
215+
let expected_third_party_balance = U256::from(9) * U256::from(10).pow(U256::from(18)); // 10 - 1 = 9
216+
let auctioneer_fee = U256::from(10).pow(U256::from(18)); // 1 PROVE
217+
assert_account_balance(&mut test, prover_address, expected_prover_balance);
218+
assert_account_balance(&mut test, third_party.address(), expected_third_party_balance);
219+
assert_account_balance(&mut test, auctioneer, auctioneer_fee);
220+
assert_withdraw_receipt(&receipt, prover_address, withdraw_amount);
221+
}
222+
223+
#[test]
224+
fn test_withdraw_third_party_insufficient_prover_balance() {
225+
let mut test = setup();
226+
let prover_address = test.fulfiller.address();
227+
let prover_owner = test.requester.address();
228+
let third_party = test.signers[0].clone();
229+
230+
// Create a prover.
231+
let create_tx = create_prover_tx(prover_address, prover_owner, U256::from(500), 0, 1, 1);
232+
test.state.execute::<MockVerifier>(&create_tx).unwrap();
233+
234+
// Give prover insufficient balance for withdrawal (only 50 PROVE).
235+
let prover_balance = U256::from(50) * U256::from(10).pow(U256::from(18));
236+
let prover_deposit = deposit_tx(prover_address, prover_balance, 0, 2, 2);
237+
test.state.execute::<MockVerifier>(&prover_deposit).unwrap();
238+
239+
// Give third party enough for fee.
240+
let third_party_balance = U256::from(10) * U256::from(10).pow(U256::from(18));
241+
let third_party_deposit = deposit_tx(third_party.address(), third_party_balance, 0, 3, 3);
242+
test.state.execute::<MockVerifier>(&third_party_deposit).unwrap();
243+
244+
// Try to withdraw 100 PROVE from prover (should fail - prover has only 50).
245+
let withdraw_amount = U256::from(100) * U256::from(10).pow(U256::from(18));
246+
let withdraw_tx = withdraw_tx(&third_party, prover_address, withdraw_amount, 0);
247+
let result = test.state.execute::<MockVerifier>(&withdraw_tx);
248+
249+
assert!(matches!(result, Err(VAppPanic::InsufficientBalance { account, amount, balance })
250+
if account == prover_address && amount == withdraw_amount && balance == prover_balance));
251+
}
252+
253+
#[test]
254+
fn test_withdraw_third_party_insufficient_fee_balance() {
255+
let mut test = setup();
256+
let prover_address = test.fulfiller.address();
257+
let prover_owner = test.requester.address();
258+
let third_party = test.signers[0].clone();
259+
260+
// Create a prover.
261+
let create_tx = create_prover_tx(prover_address, prover_owner, U256::from(500), 0, 1, 1);
262+
test.state.execute::<MockVerifier>(&create_tx).unwrap();
263+
264+
// Give prover enough balance for withdrawal.
265+
let prover_balance = U256::from(150) * U256::from(10).pow(U256::from(18));
266+
let prover_deposit = deposit_tx(prover_address, prover_balance, 0, 2, 2);
267+
test.state.execute::<MockVerifier>(&prover_deposit).unwrap();
268+
269+
// Give third party insufficient balance for fee (only 0.5 PROVE, need 1 PROVE).
270+
let third_party_balance = U256::from(5) * U256::from(10).pow(U256::from(17)); // 0.5 PROVE
271+
let third_party_deposit = deposit_tx(third_party.address(), third_party_balance, 0, 3, 3);
272+
test.state.execute::<MockVerifier>(&third_party_deposit).unwrap();
273+
274+
// Try to withdraw from prover (should fail - third party can't pay fee).
275+
let withdraw_amount = U256::from(100) * U256::from(10).pow(U256::from(18));
276+
let auctioneer_fee = U256::from(10).pow(U256::from(18)); // 1 PROVE
277+
let withdraw_tx = withdraw_tx(&third_party, prover_address, withdraw_amount, 0);
278+
let result = test.state.execute::<MockVerifier>(&withdraw_tx);
279+
280+
assert!(matches!(result, Err(VAppPanic::InsufficientBalance { account, amount, balance })
281+
if account == third_party.address() && amount == auctioneer_fee && balance == third_party_balance));
282+
}
283+
284+
#[test]
285+
fn test_withdraw_non_prover_only_self_can_withdraw() {
286+
let mut test = setup();
287+
let regular_account = test.requester.address();
288+
let third_party = test.signers[0].clone();
289+
290+
// Give regular account some balance (no prover creation).
291+
let account_balance = U256::from(100) * U256::from(10).pow(U256::from(18));
292+
let account_deposit = deposit_tx(regular_account, account_balance, 0, 1, 1);
293+
test.state.execute::<MockVerifier>(&account_deposit).unwrap();
294+
295+
// Give third party enough for fee.
296+
let third_party_balance = U256::from(10) * U256::from(10).pow(U256::from(18));
297+
let third_party_deposit = deposit_tx(third_party.address(), third_party_balance, 0, 2, 2);
298+
test.state.execute::<MockVerifier>(&third_party_deposit).unwrap();
299+
300+
// Try to have third party withdraw from regular account (should fail).
301+
let withdraw_amount = U256::from(50) * U256::from(10).pow(U256::from(18));
302+
let withdraw_tx = withdraw_tx(&third_party, regular_account, withdraw_amount, 0);
303+
let result = test.state.execute::<MockVerifier>(&withdraw_tx);
304+
305+
assert!(matches!(result, Err(VAppPanic::OnlyAccountCanWithdraw)));
306+
}

0 commit comments

Comments
 (0)