diff --git a/docs/docs/dev_docs/dapps/testing.md b/docs/docs/dev_docs/dapps/testing.md index ec59f1d7516..3551a435e69 100644 --- a/docs/docs/dev_docs/dapps/testing.md +++ b/docs/docs/dev_docs/dapps/testing.md @@ -97,7 +97,7 @@ We can have private transactions that work fine locally, but are dropped by the #### A public call fails locally -Public function calls can be caught failing locally similar to how we catch private function calls. For this example, we use a [`NativeTokenContract`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr) instead of a private one. +Public function calls can be caught failing locally similar to how we catch private function calls. For this example, we use a [`TokenContract`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr) instead of a private one. :::info Keep in mind that public function calls behave as in EVM blockchains, in that they are executed by the sequencer and not locally. Local simulation helps alert the user of a potential failure, but the actual execution path of a public function call will depend on when it gets mined. @@ -141,7 +141,7 @@ We can query the RPC server for all notes encrypted for a given user in a contra #### Querying public state -[Public state](../../concepts/foundation/state_model.md#public-state) behaves as a key-value store, much like in the EVM. This scenario is much more straightforward, in that we can directly query the target slot and get the result back as a buffer. Note that we use the [`NativeTokenContract`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr) in this example, which defines a mapping of public balances on slot 4. +[Public state](../../concepts/foundation/state_model.md#public-state) behaves as a key-value store, much like in the EVM. This scenario is much more straightforward, in that we can directly query the target slot and get the result back as a buffer. Note that we use the [`TokenContract`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr) in this example, which defines a mapping of public balances on slot 6. #include_code public-storage /yarn-project/end-to-end/src/guides/dapp_testing.test.ts typescript diff --git a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts index 204e163fa53..61c241ba411 100644 --- a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts +++ b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts @@ -90,7 +90,6 @@ EscrowContractAbi ImportTestContractAbi LendingContractAbi MultiTransferContractAbi -NativeTokenContractAbi NonNativeTokenContractAbi ParentContractAbi PendingCommitmentsContractAbi diff --git a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts index f8adaec436b..f0aaf2d38d2 100644 --- a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts +++ b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts @@ -5,13 +5,16 @@ import { CheatCodes, Fr, L2BlockL2Logs, + computeMessageSecretHash, createAccount, createAztecRpcClient, getSandboxAccountsWallets, waitForSandbox, } from '@aztec/aztec.js'; import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; -import { NativeTokenContract, PrivateTokenContract, TestContract } from '@aztec/noir-contracts/types'; +import { TestContract, TokenContract } from '@aztec/noir-contracts/types'; + +const { SANDBOX_URL = 'http://localhost:8080', ETHEREUM_HOST = 'http://localhost:8545' } = process.env; describe('guides/dapp/testing', () => { describe('on in-proc sandbox', () => { @@ -20,7 +23,7 @@ describe('guides/dapp/testing', () => { let stop: () => Promise; let owner: AccountWallet; let recipient: AccountWallet; - let token: PrivateTokenContract; + let token: TokenContract; beforeAll(async () => { // docs:start:in-proc-sandbox @@ -28,78 +31,83 @@ describe('guides/dapp/testing', () => { // docs:end:in-proc-sandbox owner = await createAccount(rpc); recipient = await createAccount(rpc); - token = await PrivateTokenContract.deploy(owner, 100n, owner.getAddress()).send().deployed(); + token = await TokenContract.deploy(owner).send().deployed(); + await token.methods._initialize({ address: owner.getAddress() }).send().wait(); }, 60_000); // docs:start:stop-in-proc-sandbox afterAll(() => stop()); // docs:end:stop-in-proc-sandbox - it('increases recipient funds on transfer', async () => { - expect(await token.methods.getBalance(recipient.getAddress()).view()).toEqual(0n); - await token.methods.transfer(20n, recipient.getAddress()).send().wait(); - expect(await token.methods.getBalance(recipient.getAddress()).view()).toEqual(20n); + it('increases recipient funds on mint', async () => { + expect(await token.methods.balance_of_private({ address: recipient.getAddress() }).view()).toEqual(0n); + const secret = Fr.random(); + const secretHash = await computeMessageSecretHash(secret); + await token.methods.mint_private(20n, secretHash).send().wait(); + await token.methods.redeem_shield({ address: recipient.getAddress() }, 20n, secret).send().wait(); + expect(await token.methods.balance_of_private({ address: recipient.getAddress() }).view()).toEqual(20n); }); }); }); describe('on local sandbox', () => { beforeAll(async () => { - const { SANDBOX_URL = 'http://localhost:8080' } = process.env; const rpc = createAztecRpcClient(SANDBOX_URL); await waitForSandbox(rpc); }); // docs:start:sandbox-example describe('private token contract', () => { - const { SANDBOX_URL = 'http://localhost:8080' } = process.env; - let rpc: AztecRPC; let owner: AccountWallet; let recipient: AccountWallet; - let token: PrivateTokenContract; + let token: TokenContract; beforeEach(async () => { rpc = createAztecRpcClient(SANDBOX_URL); owner = await createAccount(rpc); recipient = await createAccount(rpc); - token = await PrivateTokenContract.deploy(owner, 100n, owner.getAddress()).send().deployed(); + token = await TokenContract.deploy(owner).send().deployed(); + await token.methods._initialize({ address: owner.getAddress() }).send().wait(); }, 30_000); - it('increases recipient funds on transfer', async () => { - expect(await token.methods.getBalance(recipient.getAddress()).view()).toEqual(0n); - await token.methods.transfer(20n, recipient.getAddress()).send().wait(); - expect(await token.methods.getBalance(recipient.getAddress()).view()).toEqual(20n); + it('increases recipient funds on mint', async () => { + expect(await token.methods.balance_of_private({ address: recipient.getAddress() }).view()).toEqual(0n); + const secret = Fr.random(); + const secretHash = await computeMessageSecretHash(secret); + await token.methods.mint_private(20n, secretHash).send().wait(); + await token.methods.redeem_shield({ address: recipient.getAddress() }, 20n, secret).send().wait(); + expect(await token.methods.balance_of_private({ address: recipient.getAddress() }).view()).toEqual(20n); }); }); // docs:end:sandbox-example describe('private token contract with initial accounts', () => { - const { SANDBOX_URL = 'http://localhost:8080' } = process.env; - let rpc: AztecRPC; let owner: AccountWallet; let recipient: AccountWallet; - let token: PrivateTokenContract; + let token: TokenContract; beforeEach(async () => { // docs:start:use-existing-wallets rpc = createAztecRpcClient(SANDBOX_URL); [owner, recipient] = await getSandboxAccountsWallets(rpc); - token = await PrivateTokenContract.deploy(owner, 100n, owner.getAddress()).send().deployed(); + token = await TokenContract.deploy(owner).send().deployed(); + await token.methods._initialize({ address: owner.getAddress() }).send().wait(); // docs:end:use-existing-wallets }, 30_000); - it('increases recipient funds on transfer', async () => { - expect(await token.methods.getBalance(recipient.getAddress()).view()).toEqual(0n); - await token.methods.transfer(20n, recipient.getAddress()).send().wait(); - expect(await token.methods.getBalance(recipient.getAddress()).view()).toEqual(20n); + it('increases recipient funds on mint', async () => { + expect(await token.methods.balance_of_private({ address: recipient.getAddress() }).view()).toEqual(0n); + const secret = Fr.random(); + const secretHash = await computeMessageSecretHash(secret); + await token.methods.mint_private(20n, secretHash).send().wait(); + await token.methods.redeem_shield({ address: recipient.getAddress() }, 20n, secret).send().wait(); + expect(await token.methods.balance_of_private({ address: recipient.getAddress() }).view()).toEqual(20n); }); }); describe('cheats', () => { - const { SANDBOX_URL = 'http://localhost:8080', ETHEREUM_HOST = 'http://localhost:8545' } = process.env; - let rpc: AztecRPC; let owner: AccountWallet; let testContract: TestContract; @@ -122,13 +130,11 @@ describe('guides/dapp/testing', () => { }); describe('assertions', () => { - const { SANDBOX_URL = 'http://localhost:8080', ETHEREUM_HOST = 'http://localhost:8545' } = process.env; - let rpc: AztecRPC; let owner: AccountWallet; let recipient: AccountWallet; - let token: PrivateTokenContract; - let nativeToken: NativeTokenContract; + let testContract: TestContract; + let token: TokenContract; let cheats: CheatCodes; let ownerSlot: Fr; @@ -136,15 +142,20 @@ describe('guides/dapp/testing', () => { rpc = createAztecRpcClient(SANDBOX_URL); owner = await createAccount(rpc); recipient = await createAccount(rpc); - token = await PrivateTokenContract.deploy(owner, 100n, owner.getAddress()).send().deployed(); - nativeToken = await NativeTokenContract.deploy(owner, 100n, owner.getAddress()).send().deployed(); + testContract = await TestContract.deploy(owner).send().deployed(); + token = await TokenContract.deploy(owner).send().deployed(); + await token.methods._initialize({ address: owner.getAddress() }).send().wait(); + const secret = Fr.random(); + const secretHash = await computeMessageSecretHash(secret); + await token.methods.mint_private(100n, secretHash).send().wait(); + await token.methods.redeem_shield({ address: owner.getAddress() }, 100n, secret).send().wait(); // docs:start:calc-slot cheats = await CheatCodes.create(ETHEREUM_HOST, rpc); - // The balances mapping is defined on storage slot 1 and is indexed by user address - ownerSlot = cheats.aztec.computeSlotInMap(1n, owner.getAddress()); + // The balances mapping is defined on storage slot 3 and is indexed by user address + ownerSlot = cheats.aztec.computeSlotInMap(3n, owner.getAddress()); // docs:end:calc-slot - }, 30_000); + }, 60_000); it('checks private storage', async () => { // docs:start:private-storage @@ -157,40 +168,61 @@ describe('guides/dapp/testing', () => { it('checks public storage', async () => { // docs:start:public-storage - await nativeToken.methods.owner_mint_pub(owner.getAddress(), 100n).send().wait(); - const ownerPublicBalanceSlot = cheats.aztec.computeSlotInMap(4n, owner.getAddress()); - const balance = await rpc.getPublicStorageAt(nativeToken.address, ownerPublicBalanceSlot); + await token.methods.mint_public({ address: owner.getAddress() }, 100n).send().wait(); + const ownerPublicBalanceSlot = cheats.aztec.computeSlotInMap(6n, owner.getAddress()); + const balance = await rpc.getPublicStorageAt(token.address, ownerPublicBalanceSlot); expect(toBigIntBE(balance!)).toEqual(100n); // docs:end:public-storage }); - it('checks unencrypted logs', async () => { + it('checks unencrypted logs, [Kinda broken with current implementation]', async () => { // docs:start:unencrypted-logs - const tx = await nativeToken.methods.owner_mint_pub(owner.getAddress(), 100n).send().wait(); + const value = Fr.fromString('ef'); // Only 1 bytes will make its way in there :( so no larger stuff + const tx = await testContract.methods.emit_unencrypted(value).send().wait(); const logs = await rpc.getUnencryptedLogs(tx.blockNumber!, 1); - const textLogs = L2BlockL2Logs.unrollLogs(logs).map(log => log.toString('ascii')); - expect(textLogs).toEqual(['Coins minted']); + const log = L2BlockL2Logs.unrollLogs(logs)[0]; + expect(Fr.fromBuffer(log)).toEqual(value); // docs:end:unencrypted-logs }); it('asserts a local transaction simulation fails by calling simulate', async () => { // docs:start:local-tx-fails - const call = token.methods.transfer(200n, recipient.getAddress()); + const call = token.methods.transfer( + { address: owner.getAddress() }, + { address: recipient.getAddress() }, + 200n, + 0, + ); await expect(call.simulate()).rejects.toThrowError(/Balance too low/); // docs:end:local-tx-fails }); it('asserts a local transaction simulation fails by calling send', async () => { // docs:start:local-tx-fails-send - const call = token.methods.transfer(200n, recipient.getAddress()); + const call = token.methods.transfer( + { address: owner.getAddress() }, + { address: recipient.getAddress() }, + 200n, + 0, + ); await expect(call.send().wait()).rejects.toThrowError(/Balance too low/); // docs:end:local-tx-fails-send }); it('asserts a transaction is dropped', async () => { // docs:start:tx-dropped - const call1 = token.methods.transfer(80n, recipient.getAddress()); - const call2 = token.methods.transfer(50n, recipient.getAddress()); + const call1 = token.methods.transfer( + { address: owner.getAddress() }, + { address: recipient.getAddress() }, + 80n, + 0, + ); + const call2 = token.methods.transfer( + { address: owner.getAddress() }, + { address: recipient.getAddress() }, + 50n, + 0, + ); await call1.simulate(); await call2.simulate(); @@ -202,14 +234,24 @@ describe('guides/dapp/testing', () => { it('asserts a simulation for a public function call fails', async () => { // docs:start:local-pub-fails - const call = nativeToken.methods.transfer_pub(recipient.getAddress(), 1000n); - await expect(call.simulate()).rejects.toThrowError(/Balance too low/); + const call = token.methods.transfer_public( + { address: owner.getAddress() }, + { address: recipient.getAddress() }, + 1000n, + 0, + ); + await expect(call.simulate()).rejects.toThrowError(/Underflow/); // docs:end:local-pub-fails }); it('asserts a transaction with a failing public call is dropped (until we get public reverts)', async () => { // docs:start:pub-dropped - const call = nativeToken.methods.transfer_pub(recipient.getAddress(), 1000n); + const call = token.methods.transfer_public( + { address: owner.getAddress() }, + { address: recipient.getAddress() }, + 1000n, + 0, + ); await expect(call.send({ skipPublicSimulation: true }).wait()).rejects.toThrowError(/dropped/); // docs:end:pub-dropped }); diff --git a/yarn-project/noir-contracts/Nargo.toml b/yarn-project/noir-contracts/Nargo.toml index 9f6306f3b9a..6fd6bc4df17 100644 --- a/yarn-project/noir-contracts/Nargo.toml +++ b/yarn-project/noir-contracts/Nargo.toml @@ -9,7 +9,6 @@ members = [ "src/contracts/import_test_contract", "src/contracts/lending_contract", "src/contracts/multi_transfer_contract", - "src/contracts/native_token_contract", "src/contracts/non_native_token_contract", "src/contracts/parent_contract", "src/contracts/pending_commitments_contract", diff --git a/yarn-project/noir-contracts/src/contracts/native_token_contract/Nargo.toml b/yarn-project/noir-contracts/src/contracts/native_token_contract/Nargo.toml deleted file mode 100644 index 05cf9a92810..00000000000 --- a/yarn-project/noir-contracts/src/contracts/native_token_contract/Nargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "native_token_contract" -authors = [""] -compiler_version = "0.1" -type = "contract" - -[dependencies] -aztec = { path = "../../../../aztec-nr/aztec" } -value_note = { path = "../../../../aztec-nr/value-note"} -non_native = { path = "../non_native_token_contract"} \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr deleted file mode 100644 index 31dc5e610e5..00000000000 --- a/yarn-project/noir-contracts/src/contracts/native_token_contract/src/main.nr +++ /dev/null @@ -1,452 +0,0 @@ -// Testing token that can be bridged in and out. -// TODOS: -// - Add role based access control to mint functions -// - Add function for managing roles -// - Add public self-burn function for users to burn their own tokens -contract NativeToken { - // Libs - use dep::std::option::Option; - use dep::value_note::{ - balance_utils, - utils::{increment, decrement}, - value_note::{VALUE_NOTE_LEN, ValueNote, ValueNoteMethods}, - }; - use dep::std; - use dep::aztec::{ - constants_gen::GENERATOR_INDEX__SIGNATURE_PAYLOAD, - context::{PrivateContext, PublicContext, Context}, - note::{ - note_header::NoteHeader, - utils as note_utils, - }, - oracle::{ - compute_selector::compute_selector, - logs::emit_unencrypted_log, - }, - state_vars::{ - map::Map, - public_state::PublicState, - set::Set, - }, - types::type_serialisation::field_serialisation::{ - FieldSerialisationMethods, - FIELD_SERIALISED_LEN, - }, - auth::assert_valid_message_for, - }; - use dep::non_native::{ - hash::{get_mint_content_hash, get_withdraw_content_hash}, - transparent_note::{ - TransparentNote, - TransparentNoteMethods, - TRANSPARENT_NOTE_LEN, - }, - }; - - struct Storage { - balances: Map>, - total_supply: PublicState, - pending_shields: Set, - public_balances: Map>, - public_allowances: Map>>, - } - - impl Storage { - fn init(context: Context) -> pub Self { - Storage { - balances: Map::new( - context, - 1, // Storage slot - |context, slot| { - Set::new(context, slot, ValueNoteMethods) - }, - ), - total_supply: PublicState::new( - context, - 2, - FieldSerialisationMethods, - ), - pending_shields: Set::new(context, 3, TransparentNoteMethods), - public_balances: Map::new( - context, - 4, - |context, slot| { - PublicState::new( - context, - slot, - FieldSerialisationMethods, - ) - }, - ), - public_allowances: Map::new( - context, - 5, - |context, s1| { - Map::new( - context, - s1, - |context, s2| { - PublicState::new( - context, - s2, - FieldSerialisationMethods, - ) - }, - ) - }, - ), - } - } - } - - #[aztec(private)] - fn constructor( - initial_supply: Field, - owner: Field, - ) { - let storage = Storage::init(Context::private(&mut context)); - - let balance = storage.balances.at(owner); - increment(balance, initial_supply, owner); - } - - #[aztec(public)] - fn owner_mint_pub( - to: Field, - amount: Field, - ) -> Field { - let storage = Storage::init(Context::public(&mut context)); - let new_balance = storage.public_balances.at(to).read() + amount; - storage.public_balances.at(to).write(new_balance); - storage.total_supply.write(storage.total_supply.read() + amount); - let _hash = emit_unencrypted_log("Coins minted"); - - 1 - } - - #[aztec(public)] - fn owner_mint_priv( - amount: Field, - secret_hash: Field, - ) -> Field { - let storage = Storage::init(Context::public(&mut context)); - let pending_shields = storage.pending_shields; - - let mut note = TransparentNote::new(amount, secret_hash); - pending_shields.insert_from_public(&mut note); - - storage.total_supply.write(storage.total_supply.read() + amount); - - 1 - } - - // Mint Private Function - // This mint function differs to the typical token mint function as it only allows minting - // upon consuming valid messages from a token portal contract - #[aztec(private)] - fn mint( - amount: Field, - owner: Field, - // This field should be hidden - msg_key: Field, - secret: Field, - canceller: Field, - ) { - let storage = Storage::init(Context::private(&mut context)); - - let content_hash = get_mint_content_hash(amount, owner, canceller); - - // Get the l1 message from an oracle call - context.consume_l1_to_l2_message(inputs, msg_key, content_hash, secret); - - let balance = storage.balances.at(owner); - increment(balance, amount, owner); - } - - // Withdraws using user's private balance. - // @dev Destroys 2 of user's notes and sends a message to the L1 portal contract. That message can then be consumed - // by calling the `withdraw` function on the L1 portal contract (assuming the args are set correctly). - #[aztec(private)] - fn withdraw( - amount: Field, - sender: Field, - recipient: Field, // ethereum address in the field - callerOnL1: Field, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call) - ) { - let storage = Storage::init(Context::private(&mut context)); - - let sender_balance = storage.balances.at(sender); - decrement(sender_balance, amount, sender); - - let content = get_withdraw_content_hash(amount, recipient, callerOnL1); - context.message_portal(content); - } - - // Mint Public Function - // This mint function differs to the typical token mint function as it only allows minting - // upon consuming valid messages from a token portal contract - #[aztec(public)] - fn mintPublic( - amount: Field, - owner_address: Field, - // This field should be hidden - msg_key: Field, - secret: Field, - canceller: Field, - ) -> Field { - let storage = Storage::init(Context::public(&mut context)); - let public_balances = storage.public_balances; - - let content_hash = get_mint_content_hash(amount, owner_address, canceller); - - // Consume message and emit nullifier - context.consume_l1_to_l2_message(msg_key, content_hash, secret); - - // Update the public balance - let recipient_balance = public_balances.at(owner_address); - let new_balance = recipient_balance.read() + amount; - recipient_balance.write(new_balance); - - // Push the return value into the context - new_balance - } - - // Withdraws using user's public balance. - #[aztec(public)] - fn withdrawPublic( - amount: Field, - recipient: Field, - callerOnL1: Field, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call) - ) { - let storage = Storage::init(Context::public(&mut context)); - let public_balances = storage.public_balances; - - let sender = context.msg_sender(); - let sender_balance = public_balances.at(sender); - - let current_sender_balance: Field = sender_balance.read(); - - assert(current_sender_balance as u120 >= amount as u120); - let content = get_withdraw_content_hash(amount, recipient, callerOnL1); - - // Emit the l2 to l1 message - context.message_portal(content); - } - - #[aztec(public)] - fn approve( - spender: Field, - allowance: Field, - ) { - let storage = Storage::init(Context::public(&mut context)); - storage.public_allowances.at(context.msg_sender()).at(spender).write(allowance); - } - - #[aztec(public)] - fn transfer_pub( - to: Field, - amount: Field, - ) { - let storage = Storage::init(Context::public(&mut context)); - - // Decrease user's balance. - let sender = context.msg_sender(); - let sender_balance = storage.public_balances.at(sender); - let current_sender_balance: Field = sender_balance.read(); - assert(current_sender_balance as u120 >= amount as u120, "Balance too low"); - - let to_balance = storage.public_balances.at(to); - let current_to_balance: Field = to_balance.read(); - - // User has sufficient balance so we decrement it by `amount` - sender_balance.write(current_sender_balance - amount); - to_balance.write(current_to_balance + amount); - - let _hash = emit_unencrypted_log("Coins transferred"); - } - - #[aztec(public)] - fn transfer_from_pub( - from: Field, - to: Field, - amount: Field, - ) { - let storage = Storage::init(Context::public(&mut context)); - - // Decrease allowance - let allowance = storage.public_allowances.at(from).at(context.msg_sender()); - let current_allowance: Field = allowance.read(); - assert(current_allowance as u120 >= amount as u120); - allowance.write(current_allowance - amount); - - // Decrease user's balance. - let sender_balance = storage.public_balances.at(from); - let current_sender_balance: Field = sender_balance.read(); - assert(current_sender_balance as u120 >= amount as u120); - - let to_balance = storage.public_balances.at(to); - let current_to_balance: Field = to_balance.read(); - - // User has sufficient balance so we decrement it by `amount` - sender_balance.write(current_sender_balance - amount); - to_balance.write(current_to_balance + amount); - } - - // Transfers `amount` of tokens from `sender`'s private balance to a `recipient`. - // Note: Copied from PrivateToken - #[aztec(private)] - fn transfer( - from: Field, - to: Field, - amount: Field, - ) { - let storage = Storage::init(Context::private(&mut context)); - - // Gets the set of sender's notes and picks 2 of those. - let sender_balance = storage.balances.at(from); - decrement(sender_balance, amount, from); - - let balance = storage.balances.at(to); - increment(balance, amount, to); - } - - // Shield creates a way for a user to move tokens from the public context into the private context. - #[aztec(public)] - fn shield( - amount: Field, - secretHash: Field, - ) { - let storage = Storage::init(Context::public(&mut context)); - let public_balances = storage.public_balances; - let pending_shields = storage.pending_shields; - - // Decrease user's balance. - let sender = context.msg_sender(); - let sender_balance = public_balances.at(sender); - let current_sender_balance: Field = sender_balance.read(); - - assert(current_sender_balance as u120 >= amount as u120); - - // User has sufficient balance so we decrement it by `amount` - let _void1 = sender_balance.write(current_sender_balance - amount); - - // Create a commitment to the "amount" using the "secretHash" - // and insert it into the set of "pending_shields" and therefore - // (eventually) the private data tree. - let mut note = TransparentNote::new(amount, secretHash); - pending_shields.insert_from_public(&mut note); - } - - // The shield function takes a public balance, and creates a commitment containing the amount of tokens - // in the private context. - #[aztec(private)] - fn redeemShield( - amount: Field, - secret: Field, - owner: Field, - ) { - let storage = Storage::init(Context::private(&mut context)); - let pending_shields = storage.pending_shields; - - let mut public_note = TransparentNote::new_from_secret(amount, secret); - - // Ensure that the note exists in the tree and remove it. - pending_shields.assert_contains_and_remove_publicly_created(&mut public_note); - - // Mint the tokens - let balance = storage.balances.at(owner); - increment(balance, amount, owner); - } - - #[aztec(private)] - fn unshieldTokens( - from: Field, - to: Field, - amount: Field, - ) { - let storage = Storage::init(Context::private(&mut context)); - - // If `from != sender` then we use the is_valid function to check that the message is approved. - if (from != context.msg_sender()) { - // Compute the message hash, should follow eip-712 more here. - // @todo @lherskind, probably need a separate generator index and address of the - // @todo @lherskind Currently this can be used multiple times since it is not nullified. - // We can do a simple nullifier to handle that in here. Spends only 32 bytes onchain. - // @todo @LHerskind Is to be solved as part of https://github.com/AztecProtocol/aztec-packages/issues/1743 - let message_field: Field = std::hash::pedersen_with_separator([ - compute_selector("unshieldTokens(Field,Field,Field)"), - from, - to, - amount - ], - GENERATOR_INDEX__SIGNATURE_PAYLOAD - )[0]; - - assert_valid_message_for(&mut context, from, message_field); - } - - // Reduce user balance - let sender_balance = storage.balances.at(from); - decrement(sender_balance, amount, from); - - // enqueue a public function to perform the public state update. - let thisAddress = context.this_address(); - - let addUnshieldedBalance = compute_selector("addUnshieldedBalance(Field,Field)"); - let _callStackItem1 = context.call_public_function(thisAddress, addUnshieldedBalance, [amount, to]); - } - - #[aztec(public)] - internal fn addUnshieldedBalance( - amount: Field, - to: Field, - ) { - let storage = Storage::init(Context::public(&mut context)); - - let to_balance = storage.public_balances.at(to); - let current_balance = to_balance.read(); - let new_balance = current_balance + amount; - to_balance.write(new_balance); - } - - unconstrained fn balance_of( - owner: Field, - ) -> Field { - let storage = Storage::init(Context::none()); - let owner_balance = storage.balances.at(owner); - - balance_utils::get_balance(owner_balance) - } - - // Computes note hash and nullifier. - // Note 1: Needs to be defined by every contract producing logs. - // Note 2: Having it in all the contracts gives us the ability to compute the note hash and nullifier differently for different kind of notes. - unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, preimage: [Field; VALUE_NOTE_LEN]) -> [Field; 4] { - let note_header = NoteHeader { contract_address, nonce, storage_slot }; - if (storage_slot == 3) { - note_utils::compute_note_hash_and_nullifier(TransparentNoteMethods, note_header, preimage) - } else { - note_utils::compute_note_hash_and_nullifier(ValueNoteMethods, note_header, preimage) - } - } - - unconstrained fn total_supply() -> Field { - let storage = Storage::init(Context::none()); - storage.total_supply.read() - } - - unconstrained fn public_balance_of( - owner: Field, - ) -> Field { - let storage = Storage::init(Context::none()); - storage.public_balances.at(owner).read() - } - - unconstrained fn public_allowance( - owner: Field, - spender: Field, - ) -> Field { - let storage = Storage::init(Context::none()); - storage.public_allowances.at(owner).at(spender).read() - } -} diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr index 6d03ba23bd0..1a11db0ac4f 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/interface.nr @@ -67,6 +67,18 @@ impl TestPrivateContextInterface { } + fn emit_unencrypted( + self, + context: &mut PrivateContext, + value: Field + ) { + let mut serialised_args = [0; 1]; + serialised_args[0] = value; + + context.call_public_function(self.address, 0x817a64cb, serialised_args) + } + + fn getPortalContractAddress( self, context: &mut PrivateContext, @@ -200,6 +212,18 @@ impl TestPublicContextInterface { } + fn emit_unencrypted( + self, + context: PublicContext, + value: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 1]; + serialised_args[0] = value; + + context.call_public_function(self.address, 0x817a64cb, serialised_args) + } + + fn isTimeEqual( self, context: PublicContext, diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr index d2c29d6c6e8..1cfbb34248e 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr @@ -8,6 +8,7 @@ contract Test { get_public_key::get_public_key, context::get_portal_address, rand::rand, + logs::emit_unencrypted_log }, types::vec::BoundedVec, }; @@ -114,6 +115,16 @@ contract Test { } // docs:end:is-time-equal + // docs:start:emit_unencrypted_log + #[aztec(public)] + fn emit_unencrypted( + value: Field + ) -> Field { + let _hash = emit_unencrypted_log(value); + _hash[0] + } + // docs:end:emit_unencrypted_log + // Purely exists for testing unconstrained fn getRandom( kindaSeed: Field