From 0f60baf2c7989505377d816ec43c59e66e741ae9 Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 29 Jul 2024 12:38:02 +0000 Subject: [PATCH 01/11] fix: checking funded amount is enough --- .../token_with_refunds_contract/src/main.nr | 16 +++++--- .../src/types/token_note.nr | 39 +++++++++---------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr index 5cde52d8ef1..350c960ae06 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr @@ -457,7 +457,6 @@ contract TokenWithRefunds { let (fee_payer_point, user_point) = TokenNote::generate_refund_points( fee_payer_npk_m_hash, user_npk_m_hash, - funded_amount, user_randomness, fee_payer_randomness ); @@ -473,22 +472,27 @@ contract TokenWithRefunds { // function has access to the final transaction fee, which is needed to compute the actual refund amount. context.set_public_teardown_function( context.this_address(), - FunctionSelector::from_signature("complete_refund((Field,Field,bool),(Field,Field,bool))"), + FunctionSelector::from_signature("complete_refund((Field,Field,bool),(Field,Field,bool),Field)"), [ - slotted_fee_payer_point.x, slotted_fee_payer_point.y, slotted_fee_payer_point.is_infinite as Field, slotted_user_point.x, slotted_user_point.y, slotted_user_point.is_infinite as Field + slotted_fee_payer_point.x, slotted_fee_payer_point.y, slotted_fee_payer_point.is_infinite as Field, slotted_user_point.x, slotted_user_point.y, slotted_user_point.is_infinite as Field, funded_amount ] ); } #[aztec(public)] #[aztec(internal)] - fn complete_refund(fee_payer_point: Point, user_point: Point) { + fn complete_refund(fee_payer_point: Point, user_point: Point, funded_amount: Field) { // 1. We get the final note hiding points by calling a `complete_refund` function on the note. // We use 1:1 exchange rate between fee juice and token. So using `tx_fee` is enough let tx_fee = context.transaction_fee(); - let (fee_payer_note_hash, user_note_hash) = TokenNote::complete_refund(fee_payer_point, user_point, tx_fee); - // 2. At last we emit the note hashes. + // 2. We check that user funded the fee payer contract with at least the tx fee. + assert(!funded_amount.lt(tx_fee), "tx fee is higher than funded amount"); + + // 3. We complete the refund and get the note hashes. + let (fee_payer_note_hash, user_note_hash) = TokenNote::complete_refund(fee_payer_point, user_point, funded_amount, tx_fee); + + // 4. At last we emit the note hashes. context.push_note_hash(fee_payer_note_hash); context.push_note_hash(user_note_hash); // --> Once the tx is settled user and fee recipient can add the notes to their pixies. diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr index 5ba2b46b4ed..905c8cc3836 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr @@ -18,7 +18,6 @@ trait PrivatelyRefundable { fn generate_refund_points( fee_payer_npk_m_hash: Field, user_npk_m_hash: Field, - funded_amount: Field, user_randomness: Field, fee_payer_randomness: Field ) -> (Point, Point); @@ -26,6 +25,7 @@ trait PrivatelyRefundable { fn complete_refund( incomplete_fee_payer_point: Point, incomplete_user_point: Point, + funded_amount: Field, transaction_fee: Field ) -> (Field, Field); } @@ -157,7 +157,7 @@ impl OwnedNote for TokenNote { * * However we can still perform addition/subtraction on points! That is why we generate those two points, which are: * incomplete_fee_payer_point := G_npk * fee_payer_npk + G_rnd * fee_payer_randomness - * incomplete_user_point := G_npk * user_npk + G_amt * funded_amount + G_rnd * user_randomness + * incomplete_user_point := G_npk * user_npk + G_rnd * user_randomness * * where `funded_amount` is the total amount in tokens that the sponsored user initially supplied, from which * the transaction fee will be subtracted. @@ -166,6 +166,7 @@ impl OwnedNote for TokenNote { * fee as just: * * fee_point := G_amt * transaction_fee + * refund_point := G_amt * (funded_amount - transaction_fee) * * Then we arrive at the final points via addition/subtraction of that transaction fee point: * @@ -173,8 +174,8 @@ impl OwnedNote for TokenNote { * = (G_npk * fee_payer_npk + G_rnd * fee_payer_randomness) + G_amt * transaction_fee = * = G_amt * transaction_fee + G_npk * fee_payer_npk + G_rnd * fee_payer_randomness * - * user_point := incomplete_user_point - fee_point = - * = (G_amt * funded_amount + G_npk * user_npk + G_rnd + user_randomness) - G_amt * transaction_fee = + * user_point := incomplete_user_point + refund_point = + * = (G_npk * user_npk + G_rnd + user_randomness) + G_amt * (funded_amount - transaction_fee) = * = G_amt * (funded_amount - transaction_fee) + G_npk * user_npk + G_rnd + user_randomness * * The point above matches the note_hiding_point of (and therefore *is*) notes like: @@ -189,9 +190,8 @@ impl OwnedNote for TokenNote { * 1) randomness_influence = incomplete_fee_payer_point - G_npk * fee_payer_npk = * = (G_npk * fee_payer_npk + G_rnd * randomness) - G_npk * fee_payer_npk = * = G_rnd * randomness - * 2) user_fingerprint = incomplete_user_point - G_amt * funded_amount - randomness_influence = - * = (G_npk * user_npk + G_amt * funded_amount + G_rnd * randomness) - G_amt * funded_amount - * - G_rnd * randomness = + * 2) user_fingerprint = incomplete_user_point - randomness_influence = + * = (G_npk * user_npk + G_rnd * randomness) - G_rnd * randomness = * = G_npk * user_npk * 3) Then the second time the user would use this fee paying contract we would recover the same fingerprint and * link that the 2 transactions were made by the same user. Given that it's expected that only a limited set @@ -199,7 +199,7 @@ impl OwnedNote for TokenNote { * fee payer npk values of these known contracts is a feasible attack. */ impl PrivatelyRefundable for TokenNote { - fn generate_refund_points(fee_payer_npk_m_hash: Field, user_npk_m_hash: Field, funded_amount: Field, user_randomness: Field, fee_payer_randomness: Field) -> (Point, Point) { + fn generate_refund_points(fee_payer_npk_m_hash: Field, user_npk_m_hash: Field, user_randomness: Field, fee_payer_randomness: Field) -> (Point, Point) { // 1. To be able to multiply generators with randomness and npk_m_hash using barretneberg's (BB) blackbox // function we first need to convert the fields to high and low limbs. // We use the unsafe version because the multi_scalar_mul will constrain the scalars. @@ -215,35 +215,34 @@ impl PrivatelyRefundable for TokenNote { // 3. We do the necessary conversion for values relevant for the sponsored user point. // We use the unsafe version because the multi_scalar_mul will constrain the scalars. - let funded_amount_scalar = from_field_unsafe(funded_amount); let user_npk_m_hash_scalar = from_field_unsafe(user_npk_m_hash); let user_randomness_scalar = from_field_unsafe(user_randomness); - // 4. We compute `G_amt * funded_amount + G_npk * user_npk_m_hash + G_rnd * randomness`. + // 4. We compute `G_npk * user_npk_m_hash + G_rnd * randomness`. let incomplete_user_point = multi_scalar_mul( - [G_amt, G_npk, G_rnd], - [funded_amount_scalar, user_npk_m_hash_scalar, user_randomness_scalar] + [G_npk, G_rnd], + [user_npk_m_hash_scalar, user_randomness_scalar] ); // 5. At last we return the points. (incomplete_fee_payer_point, incomplete_user_point) } - fn complete_refund(incomplete_fee_payer_point: Point, incomplete_user_point: Point, transaction_fee: Field) -> (Field, Field) { + fn complete_refund(incomplete_fee_payer_point: Point, incomplete_user_point: Point, funded_amount: Field, transaction_fee: Field) -> (Field, Field) { // 1. We convert the transaction fee to high and low limbs to be able to use BB API. // We use the unsafe version because the multi_scalar_mul will constrain the scalars. let transaction_fee_scalar = from_field_unsafe(transaction_fee); + let refund_scalar = from_field_unsafe(funded_amount - transaction_fee); // 2. We compute the fee point as `G_amt * transaction_fee` - let fee_point = multi_scalar_mul( - [G_amt], - [transaction_fee_scalar] - ); + let fee_point = multi_scalar_mul([G_amt], [transaction_fee_scalar]); + + // 3. We compute the refund amount point as `G_amt * refund` + let refund_point = multi_scalar_mul([G_amt], [refund_scalar]); - // 3. Now we leverage homomorphism to privately add the fee to fee payer point and subtract it from - // the sponsored user point in public. + // 4. Now we leverage homomorphism to privately add the fee to fee payer point and we add refund to the user point. let fee_payer_point = incomplete_fee_payer_point + fee_point; - let user_point = incomplete_user_point - fee_point; + let user_point = incomplete_user_point + refund_point; assert_eq(user_point.is_infinite, false); From 1cf23b7063d555c3e4542138b7ef422c1619ecf2 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 30 Jul 2024 09:31:36 +0000 Subject: [PATCH 02/11] comment --- .../contracts/token_with_refunds_contract/src/main.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr index 350c960ae06..d6f3d83f264 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr @@ -487,7 +487,7 @@ contract TokenWithRefunds { let tx_fee = context.transaction_fee(); // 2. We check that user funded the fee payer contract with at least the tx fee. - assert(!funded_amount.lt(tx_fee), "tx fee is higher than funded amount"); + assert(!funded_amount.lt(tx_fee), "tx fee is higher than funded amount"); // funded_amout >= tx_fee // 3. We complete the refund and get the note hashes. let (fee_payer_note_hash, user_note_hash) = TokenNote::complete_refund(fee_payer_point, user_point, funded_amount, tx_fee); From 38e4b8c711adbefe1af261662ee377a68b03a4e2 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 30 Jul 2024 10:38:23 +0000 Subject: [PATCH 03/11] fixed comments --- .../token_with_refunds_contract/src/types/token_note.nr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr index 905c8cc3836..915a8fd308f 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr @@ -159,16 +159,16 @@ impl OwnedNote for TokenNote { * incomplete_fee_payer_point := G_npk * fee_payer_npk + G_rnd * fee_payer_randomness * incomplete_user_point := G_npk * user_npk + G_rnd * user_randomness * - * where `funded_amount` is the total amount in tokens that the sponsored user initially supplied, from which - * the transaction fee will be subtracted. - * * So we pass those points into the teardown function (here) and compute a third point corresponding to the transaction * fee as just: * * fee_point := G_amt * transaction_fee * refund_point := G_amt * (funded_amount - transaction_fee) * - * Then we arrive at the final points via addition/subtraction of that transaction fee point: + * where `funded_amount` is the total amount in tokens that the sponsored user initially supplied and the transaction + * fee is the final transaction fee whose value is made available in the public teardown function. + * + * Then we arrive at the final points via addition of the fee and refund points: * * fee_payer_point := incomplete_fee_payer_point + fee_point = * = (G_npk * fee_payer_npk + G_rnd * fee_payer_randomness) + G_amt * transaction_fee = From feca6fb77aefe862569de46e88d57e9f5fa36d5f Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 30 Jul 2024 10:51:15 +0000 Subject: [PATCH 04/11] moving assert to condition + fixing comments --- .../token_with_refunds_contract/src/main.nr | 20 +++++++++---------- .../src/test/basic.nr | 4 ++-- .../src/types/token_note.nr | 15 ++++++++------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr index d6f3d83f264..06911c88348 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr @@ -482,17 +482,17 @@ contract TokenWithRefunds { #[aztec(public)] #[aztec(internal)] fn complete_refund(fee_payer_point: Point, user_point: Point, funded_amount: Field) { - // 1. We get the final note hiding points by calling a `complete_refund` function on the note. - // We use 1:1 exchange rate between fee juice and token. So using `tx_fee` is enough - let tx_fee = context.transaction_fee(); - - // 2. We check that user funded the fee payer contract with at least the tx fee. - assert(!funded_amount.lt(tx_fee), "tx fee is higher than funded amount"); // funded_amout >= tx_fee - - // 3. We complete the refund and get the note hashes. - let (fee_payer_note_hash, user_note_hash) = TokenNote::complete_refund(fee_payer_point, user_point, funded_amount, tx_fee); + // 1. We get the final note hashes by calling a `complete_refund` function on the note. + // We use 1:1 exchange rate between fee juice and token so just passing transaction fee and funded amount + // to `complete_refund(...)` function is enough. + let (fee_payer_note_hash, user_note_hash) = TokenNote::complete_refund( + fee_payer_point, + user_point, + funded_amount, + context.transaction_fee() + ); - // 4. At last we emit the note hashes. + // 2. At last we emit the note hashes. context.push_note_hash(fee_payer_note_hash); context.push_note_hash(user_note_hash); // --> Once the tx is settled user and fee recipient can add the notes to their pixies. diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr index af5837f0711..104540985cb 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr @@ -41,8 +41,8 @@ unconstrained fn setup_refund_success() { // When the refund was set up, we would've spent the note worth mint_amount, and inserted a note worth //`mint_amount - funded_amount`. When completing the refund, we would've constructed a hash corresponding to a note - // worth `funded_amount - transaction_fee`. We "know" the transaction fee was 1 (it is hardcoded in TXE oracle) - // but we need to notify TXE of the note (preimage). + // worth `funded_amount - transaction_fee`. We "know" the transaction fee was 1 (it is hardcoded in + // `executePublicFunction` TXE oracle) but we need to notify TXE of the note (preimage). env.store_note_in_cache( &mut TokenNote { amount: U128::from_integer(funded_amount - 1), diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr index 915a8fd308f..097836fbb72 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr @@ -229,29 +229,32 @@ impl PrivatelyRefundable for TokenNote { } fn complete_refund(incomplete_fee_payer_point: Point, incomplete_user_point: Point, funded_amount: Field, transaction_fee: Field) -> (Field, Field) { - // 1. We convert the transaction fee to high and low limbs to be able to use BB API. + // 1. We check that user funded the fee payer contract with at least the transaction fee. + assert(!funded_amount.lt(transaction_fee), "tx fee is higher than funded amount"); // funded_amout >= transaction_fee + + // 2. We convert the transaction fee and refund amount to high and low limbs to be able to use BB API. // We use the unsafe version because the multi_scalar_mul will constrain the scalars. let transaction_fee_scalar = from_field_unsafe(transaction_fee); let refund_scalar = from_field_unsafe(funded_amount - transaction_fee); - // 2. We compute the fee point as `G_amt * transaction_fee` + // 3. We compute the fee point as `G_amt * transaction_fee` let fee_point = multi_scalar_mul([G_amt], [transaction_fee_scalar]); - // 3. We compute the refund amount point as `G_amt * refund` + // 4. We compute the refund point as `G_amt * refund` let refund_point = multi_scalar_mul([G_amt], [refund_scalar]); - // 4. Now we leverage homomorphism to privately add the fee to fee payer point and we add refund to the user point. + // 5. Now we leverage homomorphism to privately add the fee to fee payer point and we add refund to the user point. let fee_payer_point = incomplete_fee_payer_point + fee_point; let user_point = incomplete_user_point + refund_point; assert_eq(user_point.is_infinite, false); - // 4. We no longer need to do any elliptic curve operations with the points so we collapse them to the final + // 6. We no longer need to do any elliptic curve operations with the points so we collapse them to the final // note hashes. let fee_payer_note_hash = fee_payer_point.x; let user_note_hash = user_point.x; - // 5. Finally we return the hashes. + // 7. Finally we return the hashes. (fee_payer_note_hash, user_note_hash) } } From c3ec43786f980517bc659deef2daf3751bc45cb8 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 30 Jul 2024 10:53:56 +0000 Subject: [PATCH 05/11] fix --- .../token_with_refunds_contract/src/types/token_note.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr index 097836fbb72..c1eae0996fe 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr @@ -176,7 +176,7 @@ impl OwnedNote for TokenNote { * * user_point := incomplete_user_point + refund_point = * = (G_npk * user_npk + G_rnd + user_randomness) + G_amt * (funded_amount - transaction_fee) = - * = G_amt * (funded_amount - transaction_fee) + G_npk * user_npk + G_rnd + user_randomness + * = G_amt * (funded_amount - transaction_fee) + G_npk * user_npk + G_rnd * user_randomness * * The point above matches the note_hiding_point of (and therefore *is*) notes like: * { From b65794f961c4bf7702dd87876589a781dba636d7 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 30 Jul 2024 18:40:00 +0000 Subject: [PATCH 06/11] WIP on test --- .../src/test/basic.nr | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr index 104540985cb..bb8f7697fdc 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr @@ -68,3 +68,32 @@ unconstrained fn setup_refund_success() { utils::check_private_balance(token_contract_address, fee_payer, 1) } +#[test(should_fail_with = "tx fee is higher than funded amount")] +unconstrained fn setup_refund_insufficient_funded_amount() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(true); + + // Renaming owner and recipient to match naming in TokenWithRefunds + let user = owner; + let fee_payer = recipient; + + // We set funded amount to 0 to make the transaction fee higher than the funded amount + let funded_amount = 0; + let user_randomness = 42; + let fee_payer_randomness = 123; + let mut context = env.private(); + + let setup_refund_from_call_interface = TokenWithRefunds::at(token_contract_address).setup_refund( + fee_payer, + user, + funded_amount, + user_randomness, + fee_payer_randomness + ); + + authwit_cheatcodes::add_private_authwit_from_call_interface(user, fee_payer, setup_refund_from_call_interface); + + env.impersonate(fee_payer); + + // The following should fail with "tx fee is higher than funded amount" because funded amount is 0 + env.call_private_void(setup_refund_from_call_interface); +} From 8c3c8c7c58e4667466c53402c2e2e9a26158eef1 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 31 Jul 2024 07:44:44 +0000 Subject: [PATCH 07/11] nuking unnecessary assert --- .../token_with_refunds_contract/src/types/token_note.nr | 2 -- 1 file changed, 2 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr index c1eae0996fe..f5b0bc47eea 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr @@ -247,8 +247,6 @@ impl PrivatelyRefundable for TokenNote { let fee_payer_point = incomplete_fee_payer_point + fee_point; let user_point = incomplete_user_point + refund_point; - assert_eq(user_point.is_infinite, false); - // 6. We no longer need to do any elliptic curve operations with the points so we collapse them to the final // note hashes. let fee_payer_note_hash = fee_payer_point.x; From a2ad290944449d5d3e62c7622179e153e6759858 Mon Sep 17 00:00:00 2001 From: thunkar Date: Wed, 31 Jul 2024 10:46:18 +0200 Subject: [PATCH 08/11] fixed txe public reverts --- .../token_with_refunds_contract/src/test/basic.nr | 2 +- yarn-project/txe/package.json | 2 +- yarn-project/txe/src/oracle/txe_oracle.ts | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr index bb8f7697fdc..a324d61c735 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr @@ -68,7 +68,7 @@ unconstrained fn setup_refund_success() { utils::check_private_balance(token_contract_address, fee_payer, 1) } -#[test(should_fail_with = "tx fee is higher than funded amount")] +#[test(should_fail)] unconstrained fn setup_refund_insufficient_funded_amount() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(true); diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index fc21460d926..d01e249591e 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -18,7 +18,7 @@ "formatting": "run -T prettier --check ./src && run -T eslint ./src", "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests", - "dev": "DEBUG='aztec:*' LOG_LEVEL=debug && node ./dest/bin/index.js", + "dev": "DEBUG='aztec:*' LOG_LEVEL=debug node ./dest/bin/index.js", "start": "node ./dest/bin/index.js" }, "inherits": [ diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 949e181179a..f70dc666f93 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -772,8 +772,12 @@ export class TXE implements TypedOracle { const executionResult = await this.executePublicFunction(targetContractAddress, args, callContext); + if (executionResult.reverted) { + throw new Error(`Execution reverted with reason: ${executionResult.revertReason}`); + } + // Apply side effects - this.sideEffectsCounter = executionResult.endSideEffectCounter.toNumber(); + this.sideEffectsCounter += executionResult.endSideEffectCounter.toNumber(); this.setContractAddress(currentContractAddress); this.setMsgSender(currentMessageSender); this.setFunctionSelector(currentFunctionSelector); @@ -808,6 +812,10 @@ export class TXE implements TypedOracle { const executionResult = await this.executePublicFunction(targetContractAddress, args, callContext); + if (executionResult.reverted) { + throw new Error(`Execution reverted with reason: ${executionResult.revertReason}`); + } + // Apply side effects this.sideEffectsCounter += executionResult.endSideEffectCounter.toNumber(); this.setContractAddress(currentContractAddress); From 265233a03c6198cd07f3a7fd8bd6b2ab25831fd3 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 31 Jul 2024 10:59:26 +0000 Subject: [PATCH 09/11] linking issue --- .../contracts/token_with_refunds_contract/src/test/basic.nr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr index a324d61c735..a1ca536afe0 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/test/basic.nr @@ -68,6 +68,10 @@ unconstrained fn setup_refund_success() { utils::check_private_balance(token_contract_address, fee_payer, 1) } +// TODO(#7694): Ideally we would check the error message here but it's currently not possible because TXE does not +// support checking messages of errors thrown in a public teardown function. Once this is supported, check the message +// here and delete the e2e test checking it. +// #[test(should_fail_with = "tx fee is higher than funded amount")] #[test(should_fail)] unconstrained fn setup_refund_insufficient_funded_amount() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(true); From 5d3471f3e282612b58c20cdb4578fd58780dcc24 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 31 Jul 2024 11:19:07 +0000 Subject: [PATCH 10/11] added e2e test checking error --- .../src/e2e_fees/private_refunds.test.ts | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts b/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts index 8549dcee1c2..e97964a0b0e 100644 --- a/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts @@ -129,6 +129,31 @@ describe('e2e_fees/private_refunds', () => { [initialAliceBalance - tx.transactionFee!, initialBobBalance + tx.transactionFee!], ); }); + + // TODO(#7694): Remove this test once the lacking feature in TXE is implemented. + it('insufficient funded amount is correctly handled', async () => { + // 1. We generate randomness for Alice and derive randomness for Bob. + const aliceRandomness = Fr.random(); // Called user_randomness in contracts + const bobRandomness = poseidon2Hash([aliceRandomness]); // Called fee_payer_randomness in contracts + + // 2. We call arbitrary `private_get_name(...)` function to check that the fee refund flow works. + await expect(tokenWithRefunds.methods + .private_get_name() + .prove({ + fee: { + gasSettings: t.gasSettings, + paymentMethod: new PrivateRefundPaymentMethod( + tokenWithRefunds.address, + privateFPC.address, + aliceWallet, + aliceRandomness, + bobRandomness, + t.bobWallet.getAddress(), // Bob is the recipient of the fee notes. + true, // We set max fee/funded amount to zero to trigger the error. + ), + }, + })).rejects.toThrow('tx fee is higher than funded amount'); + }); }); class PrivateRefundPaymentMethod implements FeePaymentMethod { @@ -163,6 +188,12 @@ class PrivateRefundPaymentMethod implements FeePaymentMethod { * Address that the FPC sends notes it receives to. */ private feeRecipient: AztecAddress, + + /** + * If true, the max fee will be set to 0. + * TODO(#7694): Remove this param once the lacking feature in TXE is implemented. + */ + private setMaxFeeToZero = false, ) {} /** @@ -183,8 +214,9 @@ class PrivateRefundPaymentMethod implements FeePaymentMethod { * @returns The function call to pay the fee. */ async getFunctionCalls(gasSettings: GasSettings): Promise { - // we assume 1:1 exchange rate between fee juice and token. But in reality you would need to convert feeLimit (maxFee) to be in token denomination - const maxFee = gasSettings.getFeeLimit(); + // We assume 1:1 exchange rate between fee juice and token. But in reality you would need to convert feeLimit + // (maxFee) to be in token denomination. + const maxFee = this.setMaxFeeToZero ? Fr.ZERO : gasSettings.getFeeLimit(); await this.wallet.createAuthWit({ caller: this.paymentContract, From ec6fb940cf4c962a6ac14a2812d58347387934cd Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 31 Jul 2024 11:38:59 +0000 Subject: [PATCH 11/11] fmt --- .../end-to-end/src/e2e_fees/private_refunds.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts b/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts index e97964a0b0e..bc667647710 100644 --- a/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts @@ -137,9 +137,8 @@ describe('e2e_fees/private_refunds', () => { const bobRandomness = poseidon2Hash([aliceRandomness]); // Called fee_payer_randomness in contracts // 2. We call arbitrary `private_get_name(...)` function to check that the fee refund flow works. - await expect(tokenWithRefunds.methods - .private_get_name() - .prove({ + await expect( + tokenWithRefunds.methods.private_get_name().prove({ fee: { gasSettings: t.gasSettings, paymentMethod: new PrivateRefundPaymentMethod( @@ -152,7 +151,8 @@ describe('e2e_fees/private_refunds', () => { true, // We set max fee/funded amount to zero to trigger the error. ), }, - })).rejects.toThrow('tx fee is higher than funded amount'); + }), + ).rejects.toThrow('tx fee is higher than funded amount'); }); });