Skip to content

Commit

Permalink
feat: TXE::store_note_in_cache --> TXE::add_note
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan committed Sep 16, 2024
1 parent 578f67c commit 7ad3205
Show file tree
Hide file tree
Showing 10 changed files with 36 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ You can add [authwits](../writing_contracts/authwit.md) to the TXE. Here is an e

Sometimes we have to tell TXE about notes that are not generated by ourselves, but someone else. This allows us to check if we are able to decrypt them:

#include_code txe_test_store_note /noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr rust
#include_code txe_test_add_note /noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr rust

### Time traveling

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ impl TestEnvironment {
);
}

pub fn store_note_in_cache<Note, let N: u32, let M: u32>(
/// Manually adds a note to TXE. This needs to be called if you want to work with a note in your test with the note
/// not having an encrypted log emitted. TXE alternative to `PXE.addNote(...)`.
pub fn add_note<Note, let N: u32, let M: u32>(
_self: Self,
note: &mut Note,
storage_slot: Field,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ unconstrained fn transfer_to_private_to_a_different_account() {
let recipient_npk_m_hash = get_current_public_keys(&mut context, recipient).npk_m.hash();
let private_nfts_recipient_slot = derive_storage_slot_in_map(NFT::storage().private_nfts.slot, recipient);

env.store_note_in_cache(
env.add_note(
&mut NFTNote { token_id, npk_m_hash: recipient_npk_m_hash, randomness: note_randomness, header: NoteHeader::empty() },
private_nfts_recipient_slot,
nft_contract_address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub fn setup_mint_and_transfer_to_private(with_account_contracts: bool) -> (&mut
let owner_npk_m_hash = get_current_public_keys(&mut context, owner).npk_m.hash();
let private_nfts_owner_slot = derive_storage_slot_in_map(NFT::storage().private_nfts.slot, owner);

env.store_note_in_cache(
env.add_note(
&mut NFTNote {
token_id: minted_token_id,
npk_m_hash: owner_npk_m_hash,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ unconstrained fn mint_private_success() {
// Time travel so we can read keys from the registry
env.advance_block_by(6);

// Store a note in the cache so we can redeem it
env.store_note_in_cache(
// We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery.
env.add_note(
&mut TransparentNote::new(mint_amount, secret_hash),
Token::storage().pending_shields.slot,
token_contract_address
Expand Down Expand Up @@ -105,8 +105,8 @@ unconstrained fn mint_private_failure_double_spend() {
// Time travel so we can read keys from the registry
env.advance_block_by(6);

// Store a note in the cache so we can redeem it
env.store_note_in_cache(
// We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery.
env.add_note(
&mut TransparentNote::new(mint_amount, secret_hash),
Token::storage().pending_shields.slot,
token_contract_address
Expand Down Expand Up @@ -164,8 +164,8 @@ unconstrained fn mint_private_failure_overflow_recipient() {
// Time travel so we can read keys from the registry
env.advance_block_by(6);

// Store a note in the cache so we can redeem it
env.store_note_in_cache(
// We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery.
env.add_note(
&mut TransparentNote::new(mint_amount, secret_hash),
Token::storage().pending_shields.slot,
token_contract_address
Expand Down Expand Up @@ -205,12 +205,12 @@ unconstrained fn mint_private_failure_overflow_total_supply() {
env.advance_block_by(6);

// Store 2 notes in the cache so we can redeem it for owner and recipient
env.store_note_in_cache(
env.add_note(
&mut TransparentNote::new(mint_amount, secret_hash_owner),
Token::storage().pending_shields.slot,
token_contract_address
);
env.store_note_in_cache(
env.add_note(
&mut TransparentNote::new(mint_amount, secret_hash_recipient),
Token::storage().pending_shields.slot,
token_contract_address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ unconstrained fn setup_refund_success() {
//`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
// `executePublicFunction` TXE oracle) but we need to notify TXE of the note (preimage).
env.store_note_in_cache(
env.add_note(
&mut TokenNote {
amount: U128::from_integer(funded_amount - 1),
npk_m_hash: user_npk_m_hash,
Expand All @@ -53,7 +53,7 @@ unconstrained fn setup_refund_success() {
user_balances_slot,
token_contract_address
);
env.store_note_in_cache(
env.add_note(
&mut TokenNote {
amount: U128::from_integer(1),
npk_m_hash: fee_payer_npk_m_hash,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ unconstrained fn shielding_on_behalf_of_self() {
let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0);
env.call_public(shield_call_interface);

// Store a note in the cache so we can redeem it
env.store_note_in_cache(
// We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery.
env.add_note(
&mut TransparentNote::new(shield_amount, secret_hash),
Token::storage().pending_shields.slot,
token_contract_address
Expand Down Expand Up @@ -47,8 +47,8 @@ unconstrained fn shielding_on_behalf_of_other() {

// Become owner again
env.impersonate(owner);
// Store a note in the cache so we can redeem it
env.store_note_in_cache(
// We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery.
env.add_note(
&mut TransparentNote::new(shield_amount, secret_hash),
Token::storage().pending_shields.slot,
token_contract_address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, Az
// Time travel so we can read keys from the registry
env.advance_block_by(6);

// docs:start:txe_test_store_note
// Store a note in the cache so we can redeem it
env.store_note_in_cache(
// docs:start:txe_test_add_note
// We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery.
env.add_note(
&mut TransparentNote::new(mint_amount, secret_hash),
Token::storage().pending_shields.slot,
token_contract_address
);
// docs:end:txe_test_store_note
// docs:end:txe_test_add_note

// Redeem our shielded tokens
let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,14 @@ describe('e2e_blacklist_token_contract transfer private', () => {
await t.tokenSim.check();
});

it('transfer less than balance', async () => {
const balance0 = await asset.methods.balance_of_private(wallets[0].getAddress()).simulate();
const amount = balance0 / 2n;
expect(amount).toBeGreaterThan(0n);
await asset.methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, 0).send().wait();
tokenSim.transferPrivate(wallets[0].getAddress(), wallets[1].getAddress(), amount);

// We give wallets[0] access to wallets[1]'s notes to be able to check balances after the test.
wallets[0].setScopes([wallets[0].getAddress(), wallets[1].getAddress()]);
});

it('transfer to self', async () => {
const balance0 = await asset.methods.balance_of_private(wallets[0].getAddress()).simulate();
const amount = balance0 / 2n;
expect(amount).toBeGreaterThan(0n);

await asset.methods.transfer(wallets[0].getAddress(), wallets[0].getAddress(), amount, 0).send().wait();
tokenSim.transferPrivate(wallets[0].getAddress(), wallets[0].getAddress(), amount);
const {txHash} = await asset.methods.transfer(wallets[0].getAddress(), wallets[0].getAddress(), amount, 0).send().wait();
console.log('jiji txHash', txHash);
// tokenSim.transferPrivate(wallets[0].getAddress(), wallets[0].getAddress(), amount);
});

it('transfer on behalf of other', async () => {
Expand Down Expand Up @@ -69,126 +59,13 @@ describe('e2e_blacklist_token_contract transfer private', () => {
wallets[1].setScopes([wallets[1].getAddress(), wallets[0].getAddress()]);

// Perform the transfer
await action.send().wait();
tokenSim.transferPrivate(wallets[0].getAddress(), wallets[1].getAddress(), amount);

// Perform the transfer again, should fail
const txReplay = asset
.withWallet(wallets[1])
.methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, nonce)
.send();
await expect(txReplay.wait()).rejects.toThrow(DUPLICATE_NULLIFIER_ERROR);

// We give wallets[0] access to wallets[1]'s notes to be able to check balances after the test.
wallets[0].setScopes([wallets[0].getAddress(), wallets[1].getAddress()]);
});

describe('failure cases', () => {
it('transfer more than balance', async () => {
const balance0 = await asset.methods.balance_of_private(wallets[0].getAddress()).simulate();
const amount = balance0 + 1n;
expect(amount).toBeGreaterThan(0n);

await expect(
asset.methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, 0).prove(),
).rejects.toThrow('Assertion failed: Balance too low');
});

it('transfer on behalf of self with non-zero nonce', async () => {
const balance0 = await asset.methods.balance_of_private(wallets[0].getAddress()).simulate();
const amount = balance0 - 1n;
expect(amount).toBeGreaterThan(0n);

await expect(
asset.methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, 1).prove(),
).rejects.toThrow('Assertion failed: invalid nonce');
});

it('transfer more than balance on behalf of other', async () => {
const balance0 = await asset.methods.balance_of_private(wallets[0].getAddress()).simulate();
const balance1 = await asset.methods.balance_of_private(wallets[1].getAddress()).simulate();
const amount = balance0 + 1n;
const nonce = Fr.random();
expect(amount).toBeGreaterThan(0n);

// We need to compute the message we want to sign and add it to the wallet as approved
const action = asset
.withWallet(wallets[1])
.methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, nonce);

// Both wallets are connected to same node and PXE so we could just insert directly
// But doing it in two actions to show the flow.
const witness = await wallets[0].createAuthWit({ caller: wallets[1].getAddress(), action });
await wallets[1].addAuthWitness(witness);

// Perform the transfer
await expect(action.prove()).rejects.toThrow('Assertion failed: Balance too low');
expect(await asset.methods.balance_of_private(wallets[0].getAddress()).simulate()).toEqual(balance0);
expect(await asset.methods.balance_of_private(wallets[1].getAddress()).simulate()).toEqual(balance1);
});

it.skip('transfer into account to overflow', () => {
// This should already be covered by the mint case earlier. e.g., since we cannot mint to overflow, there is not
// a way to get funds enough to overflow.
// Require direct storage manipulation for us to perform a nice explicit case though.
// See https://github.com/AztecProtocol/aztec-packages/issues/1259
});

it('transfer on behalf of other without approval', async () => {
const balance0 = await asset.methods.balance_of_private(wallets[0].getAddress()).simulate();
const amount = balance0 / 2n;
const nonce = Fr.random();
expect(amount).toBeGreaterThan(0n);

// We need to compute the message we want to sign and add it to the wallet as approved
const action = asset
.withWallet(wallets[1])
.methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, nonce);
const messageHash = computeAuthWitMessageHash(
{ caller: wallets[1].getAddress(), action: action.request() },
{ chainId: wallets[0].getChainId(), version: wallets[0].getVersion() },
);

await expect(action.prove()).rejects.toThrow(`Unknown auth witness for message hash ${messageHash.toString()}`);
});

it('transfer on behalf of other, wrong designated caller', async () => {
const balance0 = await asset.methods.balance_of_private(wallets[0].getAddress()).simulate();
const amount = balance0 / 2n;
const nonce = Fr.random();
expect(amount).toBeGreaterThan(0n);

// We need to compute the message we want to sign and add it to the wallet as approved
const action = asset
.withWallet(wallets[2])
.methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, nonce);
const expectedMessageHash = computeAuthWitMessageHash(
{ caller: wallets[2].getAddress(), action: action.request() },
{ chainId: wallets[0].getChainId(), version: wallets[0].getVersion() },
);

const witness = await wallets[0].createAuthWit({ caller: wallets[1].getAddress(), action });
await wallets[2].addAuthWitness(witness);

// We give wallets[2] access to wallets[0]'s notes to test the authwit.
wallets[2].setScopes([wallets[2].getAddress(), wallets[0].getAddress()]);

await expect(action.prove()).rejects.toThrow(
`Unknown auth witness for message hash ${expectedMessageHash.toString()}`,
);
expect(await asset.methods.balance_of_private(wallets[0].getAddress()).simulate()).toEqual(balance0);
});

it('transfer from a blacklisted account', async () => {
await expect(
asset.methods.transfer(blacklisted.getAddress(), wallets[0].getAddress(), 1n, 0).prove(),
).rejects.toThrow(/Assertion failed: Blacklisted: Sender .*/);
});

it('transfer to a blacklisted account', async () => {
await expect(
asset.methods.transfer(wallets[0].getAddress(), blacklisted.getAddress(), 1n, 0).prove(),
).rejects.toThrow(/Assertion failed: Blacklisted: Recipient .*/);
});
const {txHash} = await action.send().wait();
console.log('wut txHash', txHash);
// // Perform the transfer again, should fail
// const txReplay = asset
// .withWallet(wallets[1])
// .methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, nonce)
// .send();
// await expect(txReplay.wait()).rejects.toThrow(DUPLICATE_NULLIFIER_ERROR);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
const maxBlockNumber = target.maxBlockNumber;

if (maxBlockNumber.isSome && maxBlockNumber.value < this.#globalVariables.blockNumber) {
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for low max block number`);
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for low max block number. Tx max block number: ${maxBlockNumber.value}, current block number: ${this.#globalVariables.blockNumber}.`);
return false;
} else {
return true;
Expand Down

0 comments on commit 7ad3205

Please sign in to comment.