Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: account contracts pay fee through FPC #5543

Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ contract EcdsaAccount {
}

#[aztec(private)]
#[aztec(noinitcheck)]
fn spend_private_authwit(inner_hash: Field) -> Field {
// TODO Remove noinitcheck and this get_note call once we can read nullifiers emitted in the same tx
// This function disables the init check because it could get called in the same tx
// the contract is initialized in (e.g. for fee payments)
// Manually check that the note exists. If the note does not exit, this reverts
let _ = storage.public_key.get_note();
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
actions.spend_private_authwit(inner_hash)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,14 @@ contract SchnorrAccount {
}

#[aztec(private)]
#[aztec(noinitcheck)]
fn spend_private_authwit(inner_hash: Field) -> Field {
// TODO Remove noinitcheck and this get_note call once we can read nullifiers emitted in the same tx
// This function disables the init check because it could get called in the same tx
// the contract is initialized in (e.g. for fee payments)
// Manually check that the note exists. If the note does not exit, this reverts
let _ = storage.signing_public_key.get_note();

let actions = AccountActions::private(
&mut context,
storage.approved_actions.storage_slot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ contract SchnorrHardcodedAccount {
}

#[aztec(private)]
#[aztec(noinitcheck)]
fn spend_private_authwit(inner_hash: Field) -> Field {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
actions.spend_private_authwit(inner_hash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ contract SchnorrSingleKeyAccount {
}

#[aztec(private)]
#[aztec(noinitcheck)]
fn spend_private_authwit(inner_hash: Field) -> Field {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
actions.spend_private_authwit(inner_hash)
Expand Down
120 changes: 95 additions & 25 deletions yarn-project/end-to-end/src/e2e_fees.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ describe('e2e_fees', () => {

await expect(
// this rejects if note can't be added
addPendingShieldNoteToPXE(0, RefundAmount, computeMessageSecretHash(RefundSecret), tx.txHash),
addPendingShieldNoteToPXE(aliceAddress, RefundAmount, computeMessageSecretHash(RefundSecret), tx.txHash),
).resolves.toBeUndefined();
});

Expand Down Expand Up @@ -367,7 +367,7 @@ describe('e2e_fees', () => {

await expect(
// this rejects if note can't be added
addPendingShieldNoteToPXE(0, RefundAmount, computeMessageSecretHash(RefundSecret), tx.txHash),
addPendingShieldNoteToPXE(aliceAddress, RefundAmount, computeMessageSecretHash(RefundSecret), tx.txHash),
).resolves.toBeUndefined();
});

Expand Down Expand Up @@ -431,10 +431,12 @@ describe('e2e_fees', () => {
[InitialAliceGas, InitialFPCGas - FeeAmount, InitialSequencerGas + FeeAmount],
);

await expect(addPendingShieldNoteToPXE(0, shieldedBananas, shieldSecretHash, tx.txHash)).resolves.toBeUndefined();
await expect(
addPendingShieldNoteToPXE(aliceAddress, shieldedBananas, shieldSecretHash, tx.txHash),
).resolves.toBeUndefined();

await expect(
addPendingShieldNoteToPXE(0, RefundAmount, computeMessageSecretHash(RefundSecret), tx.txHash),
addPendingShieldNoteToPXE(aliceAddress, RefundAmount, computeMessageSecretHash(RefundSecret), tx.txHash),
).resolves.toBeUndefined();
});

Expand Down Expand Up @@ -508,10 +510,12 @@ describe('e2e_fees', () => {
[InitialAliceGas, InitialFPCGas - FeeAmount, InitialSequencerGas + FeeAmount],
);

await expect(addPendingShieldNoteToPXE(0, shieldedBananas, shieldSecretHash, tx.txHash)).resolves.toBeUndefined();
await expect(
addPendingShieldNoteToPXE(aliceAddress, shieldedBananas, shieldSecretHash, tx.txHash),
).resolves.toBeUndefined();

await expect(
addPendingShieldNoteToPXE(0, RefundAmount, computeMessageSecretHash(RefundSecret), tx.txHash),
addPendingShieldNoteToPXE(aliceAddress, RefundAmount, computeMessageSecretHash(RefundSecret), tx.txHash),
).resolves.toBeUndefined();
});

Expand Down Expand Up @@ -654,8 +658,9 @@ describe('e2e_fees', () => {

describe('deploying account contracts', () => {
let accountManager: AccountManager;
let initialGas: bigint;
let initialSequencerGas: bigint;
let initialFPCGas: bigint;
let initialFPCPublicBananas: bigint;
let maxFee: bigint;
let actualFee: bigint;

Expand All @@ -664,22 +669,21 @@ describe('e2e_fees', () => {
maxFee = 3n;
actualFee = 1n;

[initialSequencerGas, initialFPCGas] = await gasBalances(sequencerAddress, bananaFPC.address);
[initialFPCPublicBananas] = await bananaPublicBalances(bananaFPC.address);
});

it('pays fee natively', async () => {
await gasBridgeTestHarness.bridgeFromL1ToL2(
BRIDGED_FPC_GAS,
BRIDGED_FPC_GAS,
accountManager.getCompleteAddress().address,
);

[initialGas, initialSequencerGas] = await gasBalances(
accountManager.getCompleteAddress().address,
sequencerAddress,
);

const [initialGas] = await gasBalances(accountManager.getCompleteAddress().address);
// account has not been deployed but it's been funded with gas
expect(initialGas).toEqual(BRIDGED_FPC_GAS);
});

it('pays fee natively', async () => {
await accountManager
.deploy({
maxFee,
Expand All @@ -692,6 +696,74 @@ describe('e2e_fees', () => {
initialSequencerGas + actualFee,
]);
});

it('pays fee privately through FPC', async () => {
const mintedPrivateBananas = 10n;
// first, register the account with the PXE so that it can receive notes
await accountManager.register();

// right now, the only way to fund an undeployed account with private notes is to transfer them
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like privately_mint_private_note should have a recipient argument, right?

// mint a note for Alice
await bananaCoin.methods.privately_mint_private_note(mintedPrivateBananas).send().wait();
// then send it over to the new, undeployed, account
await bananaCoin.methods
.transfer(aliceAddress, accountManager.getCompleteAddress().address, mintedPrivateBananas, 0n)
.send()
.wait();

const [initialPrivateBananas] = await bananaPrivateBalances(accountManager.getCompleteAddress().address);
expect(initialPrivateBananas).toEqual(mintedPrivateBananas);

const rebateSecret = Fr.random();
const tx = await accountManager
.deploy({
maxFee,
paymentMethod: new PrivateFeePaymentMethod(
bananaCoin.address,
bananaFPC.address,
await accountManager.getWallet(),
rebateSecret,
),
})
.wait();

expect(tx.status).toEqual(TxStatus.MINED);

// the new account should have paid the full fee to the FPC
await expect(bananaPrivateBalances(accountManager.getCompleteAddress().address)).resolves.toEqual([
mintedPrivateBananas - maxFee,
]);

// the FPC got paid through "unshield", so it's got a new public balance
await expect(bananaPublicBalances(bananaFPC.address)).resolves.toEqual([initialFPCPublicBananas + actualFee]);

// the FPC should have paid the sequencer
await expect(gasBalances(bananaFPC.address, sequencerAddress)).resolves.toEqual([
initialFPCGas - actualFee,
initialSequencerGas + actualFee,
]);

// the new account should have received a refund
await expect(
// this rejects if note can't be added
addPendingShieldNoteToPXE(
accountManager.getCompleteAddress().address,
maxFee - actualFee,
computeMessageSecretHash(rebateSecret),
tx.txHash,
),
).resolves.toBeUndefined();

// and it can redeem the refund
await bananaCoin.methods
.redeem_shield(accountManager.getCompleteAddress().address, maxFee - actualFee, rebateSecret)
.send()
.wait();

await expect(bananaPrivateBalances(accountManager.getCompleteAddress().address)).resolves.toEqual([
mintedPrivateBananas - actualFee,
]);
});
});

function logFunctionSignatures(artifact: ContractArtifact, logger: DebugLogger) {
Expand All @@ -708,27 +780,25 @@ describe('e2e_fees', () => {
const receipt = await bananaCoin.methods.mint_private(amount, secretHash).send().wait();

// Setup auth wit
await addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash);
await addPendingShieldNoteToPXE(aliceAddress, amount, secretHash, receipt.txHash);
const txClaim = bananaCoin.methods.redeem_shield(address, amount, secret).send();
const receiptClaim = await txClaim.wait({ debug: true });
const { visibleNotes } = receiptClaim.debugInfo!;
expect(visibleNotes[0].note.items[0].toBigInt()).toBe(amount);
};

const addPendingShieldNoteToPXE = async (accountIndex: number, amount: bigint, secretHash: Fr, txHash: TxHash) => {
const addPendingShieldNoteToPXE = async (
accountAddress: AztecAddress,
amount: bigint,
secretHash: Fr,
txHash: TxHash,
) => {
const storageSlot = new Fr(5); // The storage slot of `pending_shields` is 5.
const noteTypeId = new Fr(84114971101151129711410111011678111116101n); // TransparentNote

const note = new Note([new Fr(amount), secretHash]);
const extendedNote = new ExtendedNote(
note,
e2eContext.accounts[accountIndex].address,
bananaCoin.address,
storageSlot,
noteTypeId,
txHash,
);
await e2eContext.wallets[accountIndex].addNote(extendedNote);
const extendedNote = new ExtendedNote(note, accountAddress, bananaCoin.address, storageSlot, noteTypeId, txHash);
await e2eContext.pxe.addNote(extendedNote);
};
});

Expand Down
Loading