Skip to content

Commit

Permalink
feat!: nuking PublicToken and PrivateAirdropToken (#2873)
Browse files Browse the repository at this point in the history
Both tokens are no longer needed as the new `Token` is enough.

**Note**: I decided to not replace the `private token airdrop contract`
in private execution test because the test was pretty much identical to
the `stateful test contract` one.
  • Loading branch information
benesjan authored Oct 17, 2023
1 parent 6952a1a commit c74311d
Show file tree
Hide file tree
Showing 22 changed files with 77 additions and 1,390 deletions.
42 changes: 0 additions & 42 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -619,18 +619,6 @@ jobs:
command: cond_run_script end-to-end ./scripts/run_tests_local e2e_token_contract.test.ts
environment: { DEBUG: "aztec:*" }

e2e-private-airdrop:
machine:
image: ubuntu-2204:2023.07.2
resource_class: large
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_run_script end-to-end ./scripts/run_tests_local e2e_private_airdrop.test.ts
environment: { DEBUG: "aztec:*" }

e2e-sandbox-example:
machine:
image: ubuntu-2204:2023.07.2
Expand All @@ -643,18 +631,6 @@ jobs:
command: cond_run_script end-to-end ./scripts/run_tests_local e2e_sandbox_example.test.ts ./scripts/docker-compose-e2e-sandbox.yml
environment: { DEBUG: "aztec:*" }

e2e-multi-transfer-contract:
machine:
image: ubuntu-2204:2023.07.2
resource_class: large
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_run_script end-to-end ./scripts/run_tests_local e2e_multi_transfer.test.ts
environment: { DEBUG: "aztec:*" }

e2e-block-building:
machine:
image: ubuntu-2204:2023.07.2
Expand Down Expand Up @@ -811,18 +787,6 @@ jobs:
command: cond_run_script end-to-end ./scripts/run_tests_local integration_l1_publisher.test.ts
environment: { DEBUG: "aztec:*" }

e2e-public-token-contract:
machine:
image: ubuntu-2204:2023.07.2
resource_class: large
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_run_script end-to-end ./scripts/run_tests_local e2e_public_token_contract.test.ts
environment: { DEBUG: "aztec:*" }

e2e-cli:
machine:
image: ubuntu-2204:2023.07.2
Expand Down Expand Up @@ -1342,14 +1306,11 @@ workflows:
- e2e-deploy-contract: *e2e_test
- e2e-lending-contract: *e2e_test
- e2e-token-contract: *e2e_test
- e2e-private-airdrop: *e2e_test
- e2e-sandbox-example: *e2e_test
- e2e-multi-transfer-contract: *e2e_test
- e2e-block-building: *e2e_test
- e2e-nested-contract: *e2e_test
- e2e-non-contract-account: *e2e_test
- e2e-multiple-accounts-1-enc-key: *e2e_test
- e2e-public-token-contract: *e2e_test
- e2e-cli: *e2e_test
- e2e-cross-chain-messaging: *e2e_test
- e2e-public-cross-chain-messaging: *e2e_test
Expand Down Expand Up @@ -1380,14 +1341,11 @@ workflows:
- e2e-deploy-contract
- e2e-lending-contract
- e2e-token-contract
- e2e-private-airdrop
- e2e-sandbox-example
- e2e-multi-transfer-contract
- e2e-block-building
- e2e-nested-contract
- e2e-non-contract-account
- e2e-multiple-accounts-1-enc-key
- e2e-public-token-contract
- e2e-cli
- e2e-cross-chain-messaging
- e2e-public-cross-chain-messaging
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/dev_docs/contracts/syntax/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ They can be emitted by both public and private functions.

To emit unencrypted logs first import the `emit_unencrypted_log` utility function inside your contract:

#include_code unencrypted_import /yarn-project/noir-contracts/src/contracts/public_token_contract/src/main.nr rust
#include_code unencrypted_import /yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr rust

Then you can call the function:

#include_code unencrypted_log /yarn-project/noir-contracts/src/contracts/public_token_contract/src/main.nr rust
#include_code emit_unencrypted /yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr rust

Once emitted, unencrypted events are stored in AztecNode and can be queried by anyone:
<Tabs groupId="events">
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/dev_docs/contracts/syntax/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ While `staticcall` and `delegatecall` both have flags in the call context, they

- A special `constructor` function MUST be declared within a contract's scope.
- A constructor doesn't have a name, because its purpose is clear: to initialize contract state.
- In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function, in the current version of the sandbox it cannot call public functions either).
- In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function).
- A constructor behaves almost identically to any other function. It's just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract.

An example of a somewhat boring constructor is as follows:

#include_code empty-constructor /yarn-project/noir-contracts/src/contracts/public_token_contract/src/main.nr rust
#include_code empty-constructor /yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr rust

Although you can have a constructor that does nothing, you might want to do something with it, such as setting the deployer as an owner.

Expand Down
21 changes: 5 additions & 16 deletions docs/docs/dev_docs/tutorials/writing_dapp/contract_interaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { readFileSync } from "fs";
import TokenContractArtifact from "../contracts/token/target/Token.json" assert { type: "json" };
```

And then add the following code for initialising the `Contract` instances:
And then add the following code for initializing the `Contract` instances:

#include_code get-tokens yarn-project/end-to-end/src/sample-dapp/contracts.mjs javascript

Expand Down Expand Up @@ -93,15 +93,16 @@ At the time of this writing, there are no events emitted when new private notes

## Working with public state

While they are [fundamentally differently](../../../concepts/foundation/state_model.md), the API for working with private and public functions and state from `aztec.js` is equivalent. To query the balance in public tokens for our user accounts, we can just call the `publicBalanceOf` view function in the contract:
While they are [fundamentally differently](../../../concepts/foundation/state_model.md), the API for working with private and public functions and state from `aztec.js` is equivalent. To query the balance in public tokens for our user accounts, we can just call the `balance_of_public` view function in the contract:

#include_code showPublicBalances yarn-project/end-to-end/src/sample-dapp/index.mjs javascript

:::info
Since this is a public token contract we are working with, we can now query the balance for any address, not just those registered in our local PXE. We can also send funds to addresses for which we don't know their [public encryption key](../../../concepts/foundation/accounts/keys.md#encryption-keys).
Since this we are working with pubic balances, we can now query the balance for any address, not just those registered in our local PXE. We can also send funds to addresses for which we don't know their [public encryption key](../../../concepts/foundation/accounts/keys.md#encryption-keys).
:::

Here, since the public token contract does not mint any initial funds upon deployment, the balances for all of our user's accounts will be zero. But we can send a transaction to mint tokens to change this, using very similar code to the one for sending private funds:
Here, since the token contract does not mint any initial funds upon deployment, the balances for all of our user's accounts will be zero.
But we can send a transaction to mint tokens, using very similar code to the one for sending private funds:

#include_code mintPublicFunds yarn-project/end-to-end/src/sample-dapp/index.mjs javascript

Expand All @@ -121,18 +122,6 @@ Balance of 0x226f8087792beff8d5009eb94e65d2a4a505b70baf4a9f28d33c8d620b0ba972: 0
Balance of 0x0e1f60e8566e2c6d32378bdcadb7c63696e853281be798c107266b8c3a88ea9b: 0
```

Public functions can emit [unencrypted public logs](../../contracts/syntax/events.md#unencrypted-events), which we can query via the PXE interface. In particular, the public token contract emits a generic `Coins minted` whenever the `mint` method is called:

#include_code unencrypted_log yarn-project/noir-contracts/src/contracts/public_token_contract/src/main.nr rust

We can extend our code by querying the logs emitted on the last block when the minting transaction is mined:

#include_code showLogs yarn-project/end-to-end/src/sample-dapp/index.mjs javascript

:::info
At the time of this writing, there is no event-based mechanism in the `aztec.js` library to subscribe to events. The only option to consume them is to poll on every new block detected. This will change in a future version.
:::

## Next steps

In the next and final section, we'll [set up automated tests for our application](./testing.md).
186 changes: 2 additions & 184 deletions yarn-project/acir-simulator/src/client/private_execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import {
ImportTestContractArtifact,
ParentContractArtifact,
PendingCommitmentsContractArtifact,
PrivateTokenAirdropContractArtifact,
StatefulTestContractArtifact,
TestContractArtifact,
TokenContractArtifact,
Expand Down Expand Up @@ -191,188 +190,7 @@ describe('Private Execution test suite', () => {
});
});

describe('private token airdrop contract', () => {
const contractAddress = defaultContractAddress;
const mockFirstNullifier = new Fr(1111);
let currentNoteIndex = 0n;

const buildNote = (amount: bigint, owner: AztecAddress, storageSlot = Fr.random()) => {
// WARNING: this is not actually how nonces are computed!
// For the purpose of this test we use a mocked firstNullifier and and a random number
// to compute the nonce. Proper nonces are only enforced later by the kernel/later circuits
// which are not relevant to this test. In practice, the kernel first squashes all transient
// noteHashes with their matching nullifiers. It then reorders the remaining "persistable"
// noteHashes. A TX's real first nullifier (generated by the initial kernel) and a noteHash's
// array index at the output of the final kernel/ordering circuit are used to derive nonce via:
// `hash(firstNullifier, noteHashIndex)`
const noteHashIndex = Math.floor(Math.random()); // mock index in TX's final newNoteHashes array
const nonce = computeCommitmentNonce(circuitsWasm, mockFirstNullifier, noteHashIndex);
const preimage = [new Fr(amount), owner.toField(), Fr.random()];
const innerNoteHash = Fr.fromBuffer(hash(preimage.map(p => p.toBuffer())));
return {
contractAddress,
storageSlot,
nonce,
preimage,
innerNoteHash,
siloedNullifier: new Fr(0),
index: currentNoteIndex++,
};
};

beforeEach(() => {
oracle.getCompleteAddress.mockImplementation((address: AztecAddress) => {
if (address.equals(owner)) return Promise.resolve(ownerCompleteAddress);
if (address.equals(recipient)) return Promise.resolve(recipientCompleteAddress);
throw new Error(`Unknown address ${address}`);
});

oracle.getFunctionArtifact.mockImplementation((_, selector) =>
Promise.resolve(
PrivateTokenAirdropContractArtifact.functions.find(f =>
selector.equals(FunctionSelector.fromNameAndParameters(f.name, f.parameters)),
)!,
),
);
});

it('should have an artifact for computing note hash and nullifier', async () => {
const storageSlot = Fr.random();
const note = buildNote(60n, owner, storageSlot);

// Should be the same as how we compute the values for the ValueNote in the Aztec.nr library.
const valueNoteHash = hashFields(note.preimage);
const innerNoteHash = hashFields([storageSlot, valueNoteHash]);
const siloedNoteHash = siloCommitment(circuitsWasm, contractAddress, innerNoteHash);
const uniqueSiloedNoteHash = computeUniqueCommitment(circuitsWasm, note.nonce, siloedNoteHash);
const innerNullifier = hashFields([uniqueSiloedNoteHash, ownerPk.low, ownerPk.high]);

const result = await acirSimulator.computeNoteHashAndNullifier(
contractAddress,
note.nonce,
storageSlot,
note.preimage,
);

expect(result).toEqual({
innerNoteHash,
siloedNoteHash,
uniqueSiloedNoteHash,
innerNullifier,
});
});

it('should a constructor with arguments that inserts notes', async () => {
const artifact = getFunctionArtifact(PrivateTokenAirdropContractArtifact, 'constructor');

const result = await runSimulator({ args: [140, owner], artifact });

expect(result.newNotes).toHaveLength(1);
const newNote = result.newNotes[0];
expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm));

const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO));
expect(newCommitments).toHaveLength(1);

const [commitment] = newCommitments;
expect(commitment).toEqual(
await acirSimulator.computeInnerNoteHash(contractAddress, newNote.storageSlot, newNote.preimage),
);
});

it('should run the mint function', async () => {
const artifact = getFunctionArtifact(PrivateTokenAirdropContractArtifact, 'mint');

const result = await runSimulator({ args: [140, owner], artifact });

expect(result.newNotes).toHaveLength(1);
const newNote = result.newNotes[0];
expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm));

const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO));
expect(newCommitments).toHaveLength(1);

const [commitment] = newCommitments;
expect(commitment).toEqual(
await acirSimulator.computeInnerNoteHash(contractAddress, newNote.storageSlot, newNote.preimage),
);
});

it('should run the transfer function', async () => {
const amountToTransfer = 100n;
const artifact = getFunctionArtifact(PrivateTokenAirdropContractArtifact, 'transfer');

const storageSlot = computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm);
const recipientStorageSlot = computeSlotForMapping(new Fr(1n), recipient.toField(), circuitsWasm);

const notes = [buildNote(60n, owner, storageSlot), buildNote(80n, owner, storageSlot)];
oracle.getNotes.mockResolvedValue(notes);

const consumedNotes = await asyncMap(notes, ({ nonce, preimage }) =>
acirSimulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage),
);
await insertLeaves(consumedNotes.map(n => n.siloedNoteHash));

const args = [amountToTransfer, recipient];
const result = await runSimulator({ args, artifact, msgSender: owner });

// The two notes were nullified
const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toHaveLength(consumedNotes.length);
expect(newNullifiers).toEqual(expect.arrayContaining(consumedNotes.map(n => n.innerNullifier)));

expect(result.newNotes).toHaveLength(2);
const [changeNote, recipientNote] = result.newNotes;
expect(recipientNote.storageSlot).toEqual(recipientStorageSlot);

const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO));
expect(newCommitments).toHaveLength(2);

const [changeNoteCommitment, recipientNoteCommitment] = newCommitments;
expect(recipientNoteCommitment).toEqual(
await acirSimulator.computeInnerNoteHash(contractAddress, recipientStorageSlot, recipientNote.preimage),
);
expect(changeNoteCommitment).toEqual(
await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, changeNote.preimage),
);

expect(recipientNote.preimage[0]).toEqual(new Fr(amountToTransfer));
expect(changeNote.preimage[0]).toEqual(new Fr(40n));

const readRequests = result.callStackItem.publicInputs.readRequests.filter(field => !field.equals(Fr.ZERO));
expect(readRequests).toHaveLength(consumedNotes.length);
expect(readRequests).toEqual(expect.arrayContaining(consumedNotes.map(n => n.uniqueSiloedNoteHash)));
});

it('should be able to transfer with dummy notes', async () => {
const amountToTransfer = 100n;
const balance = 160n;
const artifact = getFunctionArtifact(PrivateTokenAirdropContractArtifact, 'transfer');

const storageSlot = computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm);

const notes = [buildNote(balance, owner, storageSlot)];
oracle.getNotes.mockResolvedValue(notes);

const consumedNotes = await asyncMap(notes, ({ nonce, preimage }) =>
acirSimulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage),
);
await insertLeaves(consumedNotes.map(n => n.siloedNoteHash));

const args = [amountToTransfer, recipient];
const result = await runSimulator({ args, artifact, msgSender: owner });

const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toEqual(consumedNotes.map(n => n.innerNullifier));

expect(result.newNotes).toHaveLength(2);
const [changeNote, recipientNote] = result.newNotes;
expect(recipientNote.preimage[0]).toEqual(new Fr(amountToTransfer));
expect(changeNote.preimage[0]).toEqual(new Fr(balance - amountToTransfer));
});
});

describe('stateful test contract contract', () => {
describe('stateful test contract', () => {
const contractAddress = defaultContractAddress;
const mockFirstNullifier = new Fr(1111);
let currentNoteIndex = 0n;
Expand Down Expand Up @@ -443,7 +261,7 @@ describe('Private Execution test suite', () => {
});
});

it('should a constructor with arguments that inserts notes', async () => {
it('should have a constructor with arguments that inserts notes', async () => {
const artifact = getFunctionArtifact(StatefulTestContractArtifact, 'constructor');

const result = await runSimulator({ args: [owner, 140], artifact });
Expand Down
Loading

0 comments on commit c74311d

Please sign in to comment.