Skip to content
Merged
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
18 changes: 16 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,18 @@ jobs:
- run:
name: "Test"
command: AVM_ENABLED=1 cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_avm_simulator.test.ts

e2e-fees:
docker:
- image: aztecprotocol/alpine-build-image
resource_class: small
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_fees.test.ts

pxe:
docker:
- image: aztecprotocol/alpine-build-image
Expand All @@ -916,7 +928,7 @@ jobs:
- run:
name: "Test"
command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=cli_docs_sandbox.test.ts

e2e-docs-examples:
docker:
- image: aztecprotocol/alpine-build-image
Expand All @@ -926,7 +938,7 @@ jobs:
- *setup_env
- run:
name: "Test"
command: AVM_ENABLED=1 cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=docs_examples_test.ts
command: AVM_ENABLED=1 cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=docs_examples_test.ts

guides-writing-an-account-contract:
docker:
Expand Down Expand Up @@ -1362,6 +1374,7 @@ workflows:
- e2e-browser: *e2e_test
- e2e-card-game: *e2e_test
- e2e-avm-simulator: *e2e_test
- e2e-fees: *e2e_test
- pxe: *e2e_test
- cli-docs-sandbox: *e2e_test
- e2e-docs-examples: *e2e_test
Expand Down Expand Up @@ -1406,6 +1419,7 @@ workflows:
- e2e-browser
- e2e-card-game
- e2e-avm-simulator
- e2e-fees
- pxe
- boxes-blank
- boxes-blank-react
Expand Down
1 change: 1 addition & 0 deletions noir-projects/noir-contracts/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ members = [
"contracts/easy_private_voting_contract",
"contracts/ecdsa_account_contract",
"contracts/escrow_contract",
"contracts/gas_token_contract",
"contracts/import_test_contract",
"contracts/inclusion_proofs_contract",
"contracts/lending_contract",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "gas_token_contract"
authors = [""]
compiler_version = ">=0.18.0"
type = "contract"

[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
safe_math = { path = "../../../aztec-nr/safe-math" }
authwit = { path = "../../../aztec-nr/authwit" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use dep::safe_math::SafeU120;
use dep::aztec::context::PublicContext;

pub fn calculate_fee(_context: PublicContext) -> SafeU120 {
SafeU120::new(1)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
mod fee;

contract GasToken {
use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress};
use dep::aztec::{hash::{compute_secret_hash}, state_vars::{public_state::PublicState, map::Map}};

use dep::safe_math::SafeU120;

use crate::fee::calculate_fee;

struct Storage {
balances: Map<AztecAddress, PublicState<SafeU120>>,
}

#[aztec(private)]
fn constructor() {}

#[aztec(public)]
fn redeem_bridged_balance(amount: Field) {
// mock
let amount_u120 = SafeU120::new(amount);
let new_balance = storage.balances.at(context.msg_sender()).read().add(amount_u120);
storage.balances.at(context.msg_sender()).write(new_balance);
}

#[aztec(public)]
fn check_balance(fee_limit: Field) {
let fee_limit_u120 = SafeU120::new(fee_limit);
assert(storage.balances.at(context.msg_sender()).read().ge(fee_limit_u120), "Balance too low");
}

#[aztec(public)]
fn pay_fee(fee_limit: Field) -> Field {
let fee_limit_u120 = SafeU120::new(fee_limit);
let fee = calculate_fee(context);
assert(fee.le(fee_limit_u120), "Fee too high");

let sender_new_balance = storage.balances.at(context.msg_sender()).read().sub(fee);
storage.balances.at(context.msg_sender()).write(sender_new_balance);

let recipient_new_balance = storage.balances.at(context.fee_recipient()).read().add(fee);
storage.balances.at(context.fee_recipient()).write(recipient_new_balance);

let rebate = fee_limit_u120.sub(fee);
rebate.value as Field
}

// utility function for testing
unconstrained fn balance_of(owner: AztecAddress) -> pub Field {
storage.balances.at(owner).read().value as Field
}

// TODO: remove this placeholder once https://github.com/AztecProtocol/aztec-packages/issues/2918 is implemented
unconstrained fn compute_note_hash_and_nullifier(
contract_address: AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
serialized_note: [Field; 0]
) -> pub [Field; 4] {
[0, 0, 0, 0]
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PXE, Tx, TxExecutionRequest } from '@aztec/circuit-types';

import { FeeOptions } from '../account/interface.js';
import { SentTx } from './sent_tx.js';

/**
Expand All @@ -11,6 +12,11 @@ export type SendMethodOptions = {
* Wether to skip the simulation of the public part of the transaction.
*/
skipPublicSimulation?: boolean;

/**
* The fee options for the transaction.
*/
fee?: FeeOptions;
};

/**
Expand Down
7 changes: 4 additions & 3 deletions yarn-project/aztec.js/src/contract/batch_call.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FunctionCall, TxExecutionRequest } from '@aztec/circuit-types';

import { Wallet } from '../account/index.js';
import { BaseContractInteraction } from './base_contract_interaction.js';
import { BaseContractInteraction, SendMethodOptions } from './base_contract_interaction.js';

/** A batch of function calls to be sent as a single transaction through a wallet. */
export class BatchCall extends BaseContractInteraction {
Expand All @@ -12,11 +12,12 @@ export class BatchCall extends BaseContractInteraction {
/**
* Create a transaction execution request that represents this batch, encoded and authenticated by the
* user's wallet, ready to be simulated.
* @param opts - An optional object containing additional configuration for the transaction.
* @returns A Promise that resolves to a transaction instance.
*/
public async create(): Promise<TxExecutionRequest> {
public async create(opts?: SendMethodOptions): Promise<TxExecutionRequest> {
if (!this.txRequest) {
this.txRequest = await this.wallet.createTxExecutionRequest(this.calls);
this.txRequest = await this.wallet.createTxExecutionRequest(this.calls, opts?.fee);
}
return this.txRequest;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ export class ContractFunctionInteraction extends BaseContractInteraction {
/**
* Create a transaction execution request that represents this call, encoded and authenticated by the
* user's wallet, ready to be simulated.
* @param opts - An optional object containing additional configuration for the transaction.
* @returns A Promise that resolves to a transaction instance.
*/
public async create(): Promise<TxExecutionRequest> {
public async create(opts?: SendMethodOptions): Promise<TxExecutionRequest> {
if (this.functionDao.functionType === FunctionType.UNCONSTRAINED) {
throw new Error("Can't call `create` on an unconstrained function.");
}
if (!this.txRequest) {
this.txRequest = await this.wallet.createTxExecutionRequest([this.request()]);
this.txRequest = await this.wallet.createTxExecutionRequest([this.request()], opts?.fee);
}
return this.txRequest;
}
Expand Down
5 changes: 2 additions & 3 deletions yarn-project/aztec.js/src/fee/native_fee_payment_method.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { FunctionCall } from '@aztec/circuit-types';
import { FunctionData } from '@aztec/circuits.js';
import { FunctionSelector } from '@aztec/foundation/abi';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { GasTokenAddress } from '@aztec/protocol-contracts/gas-token';

import { FeePaymentMethod } from './fee_payment_method.js';

/**
* Pay fee directly in the native gas token.
*/
export class NativeFeePaymentMethod implements FeePaymentMethod {
// TODO(fees) replace this with the address of the gas token when that's deployed.
static #GAS_TOKEN = AztecAddress.ZERO;
static #GAS_TOKEN = GasTokenAddress;

constructor() {}

Expand Down
1 change: 1 addition & 0 deletions yarn-project/end-to-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@aztec/merkle-tree": "workspace:^",
"@aztec/noir-contracts.js": "workspace:^",
"@aztec/p2p": "workspace:^",
"@aztec/protocol-contracts": "workspace:^",
"@aztec/pxe": "workspace:^",
"@aztec/sequencer-client": "workspace:^",
"@aztec/types": "workspace:^",
Expand Down
1 change: 1 addition & 0 deletions yarn-project/end-to-end/src/cli_docs_sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ EasyPrivateTokenContractArtifact
EasyPrivateVotingContractArtifact
EcdsaAccountContractArtifact
EscrowContractArtifact
GasTokenContractArtifact
ImportTestContractArtifact
InclusionProofsContractArtifact
LendingContractArtifact
Expand Down
66 changes: 66 additions & 0 deletions yarn-project/end-to-end/src/e2e_fees.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { AztecAddress, ContractDeployer, NativeFeePaymentMethod } from '@aztec/aztec.js';
import { GasTokenContract, TokenContract } from '@aztec/noir-contracts.js';
import { getCanonicalGasToken } from '@aztec/protocol-contracts/gas-token';

import { setup } from './fixtures/utils.js';

describe('e2e_fees', () => {
let aliceAddress: AztecAddress;
let _bobAddress: AztecAddress;
let sequencerAddress: AztecAddress;
let gasTokenContract: GasTokenContract;
let testContract: TokenContract;

beforeAll(async () => {
process.env.PXE_URL = '';
const { accounts, aztecNode, wallet } = await setup(3);

await aztecNode.setConfig({
feeRecipient: accounts.at(-1)!.address,
});
const canonicalGasToken = getCanonicalGasToken();
const deployer = new ContractDeployer(canonicalGasToken.artifact, wallet);
const { contract } = await deployer
.deploy()
.send({
contractAddressSalt: canonicalGasToken.instance.salt,
})
.wait();

gasTokenContract = contract as GasTokenContract;
aliceAddress = accounts.at(0)!.address;
_bobAddress = accounts.at(1)!.address;
sequencerAddress = accounts.at(-1)!.address;

testContract = await TokenContract.deploy(wallet, aliceAddress, 'Test', 'TEST', 1).send().deployed();

// Alice gets a balance of 1000 gas token
await gasTokenContract.methods.redeem_bridged_balance(1000).send().wait();
}, 100_000);

it('deploys gas token contract at canonical address', () => {
expect(gasTokenContract.address).toEqual(getCanonicalGasToken().address);
});

describe('NativeFeePaymentMethod', () => {
it('pays out the expected fee to the sequencer', async () => {
await testContract.methods
.mint_public(aliceAddress, 1000)
.send({
fee: {
maxFee: 1,
paymentMethod: new NativeFeePaymentMethod(),
},
})
.wait();

const [sequencerBalance, aliceBalance] = await Promise.all([
gasTokenContract.methods.balance_of(sequencerAddress).view(),
gasTokenContract.methods.balance_of(aliceAddress).view(),
]);

expect(sequencerBalance).toEqual(1n);
expect(aliceBalance).toEqual(999n);
});
});
});
1 change: 1 addition & 0 deletions yarn-project/end-to-end/src/fixtures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ async function setupWithRemoteEnvironment(
};
const cheatCodes = CheatCodes.create(config.rpcUrl, pxeClient!);
const teardown = () => Promise.resolve();

return {
aztecNode,
sequencer: undefined,
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/end-to-end/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
{
"path": "../p2p"
},
{
"path": "../protocol-contracts"
},
{
"path": "../pxe"
},
Expand Down
8 changes: 6 additions & 2 deletions yarn-project/protocol-contracts/scripts/copy-contracts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
set -euo pipefail
mkdir -p ./src/artifacts

contracts=(contract_class_registerer_contract-ContractClassRegisterer contract_instance_deployer_contract-ContractInstanceDeployer)
contracts=(
contract_class_registerer_contract-ContractClassRegisterer
contract_instance_deployer_contract-ContractInstanceDeployer
gas_token_contract-GasToken
)

for contract in "${contracts[@]}"; do
cp "../noir-contracts.js/artifacts/$contract.json" ./src/artifacts/${contract#*-}.json
done

yarn run -T prettier -w ./src/artifacts
yarn run -T prettier -w ./src/artifacts
6 changes: 6 additions & 0 deletions yarn-project/protocol-contracts/src/gas-token/artifact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { loadContractArtifact } from '@aztec/types/abi';
import { NoirCompiledContract } from '@aztec/types/noir';

import GasTokenJson from '../artifacts/GasToken.json' assert { type: 'json' };

export const GasTokenArtifact = loadContractArtifact(GasTokenJson as NoirCompiledContract);
8 changes: 8 additions & 0 deletions yarn-project/protocol-contracts/src/gas-token/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GasTokenAddress, getCanonicalGasToken } from './index.js';

describe('GasToken', () => {
it('returns canonical protocol contract', () => {
const contract = getCanonicalGasToken();
expect(contract.address.toString()).toEqual(GasTokenAddress.toString());
});
});
9 changes: 9 additions & 0 deletions yarn-project/protocol-contracts/src/gas-token/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ProtocolContract, getCanonicalProtocolContract } from '../protocol_contract.js';
import { GasTokenArtifact } from './artifact.js';

/** Returns the canonical deployment of the gas token. */
export function getCanonicalGasToken(): ProtocolContract {
return getCanonicalProtocolContract(GasTokenArtifact, 1);
}

export const GasTokenAddress = getCanonicalGasToken().address;
3 changes: 2 additions & 1 deletion yarn-project/pxe/src/pxe_service/create_pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TestKeyStore } from '@aztec/key-store';
import { AztecLmdbStore } from '@aztec/kv-store/lmdb';
import { initStoreForRollup } from '@aztec/kv-store/utils';
import { getCanonicalClassRegisterer } from '@aztec/protocol-contracts/class-registerer';
import { getCanonicalGasToken } from '@aztec/protocol-contracts/gas-token';
import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer';

import { join } from 'path';
Expand Down Expand Up @@ -45,7 +46,7 @@ export async function createPXEService(
const db = new KVPxeDatabase(await initStoreForRollup(AztecLmdbStore.open(pxeDbPath), l1Contracts.rollupAddress));

const server = new PXEService(keyStore, aztecNode, db, config, logSuffix);
await server.addContracts([getCanonicalClassRegisterer(), getCanonicalInstanceDeployer()]);
await server.addContracts([getCanonicalClassRegisterer(), getCanonicalInstanceDeployer(), getCanonicalGasToken()]);

await server.start();
return server;
Expand Down
1 change: 1 addition & 0 deletions yarn-project/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ __metadata:
"@aztec/merkle-tree": "workspace:^"
"@aztec/noir-contracts.js": "workspace:^"
"@aztec/p2p": "workspace:^"
"@aztec/protocol-contracts": "workspace:^"
"@aztec/pxe": "workspace:^"
"@aztec/sequencer-client": "workspace:^"
"@aztec/types": "workspace:^"
Expand Down