Skip to content

Commit

Permalink
fix: canonical contract address (#5030)
Browse files Browse the repository at this point in the history
Compute stable canonical contract addresses by sorting public functions
before committing to them.

Fix #5024 
Built on top of #4983
  • Loading branch information
alexghr authored Mar 8, 2024
1 parent c255255 commit b2af880
Show file tree
Hide file tree
Showing 12 changed files with 413 additions and 101,497 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ contract AppSubscription {
SharedImmutable
};

use dep::aztec::protocol_types::traits::is_empty;

use dep::aztec::{context::Context, oracle::get_public_key::get_public_key};
use dep::authwit::{account::AccountActions, auth_witness::get_auth_witness, auth::assert_current_call_valid_authwit};

Expand All @@ -29,7 +31,7 @@ contract AppSubscription {

global SUBSCRIPTION_DURATION_IN_BLOCKS = 5;
global SUBSCRIPTION_TXS = 5;
// global GAS_TOKEN_ADDRESS = AztecAddress::from_field(0x08c0e8041f92758ca49ccb62a77318b46090019d380552ddaec5cd0b54804636);
global CANONICAL_GAS_TOKEN_ADDRESS = AztecAddress::from_field(0x22a29a9544c0cbdbc35bb21caa78b6f08903be4b390a65319e33afd7443f3499);

#[aztec(private)]
fn entrypoint(payload: pub DAppPayload, user_address: pub AztecAddress) {
Expand Down Expand Up @@ -79,7 +81,13 @@ contract AppSubscription {
storage.subscription_token_address.initialize(subscription_token_address);
storage.subscription_recipient_address.initialize(subscription_recipient_address);
storage.subscription_price.initialize(subscription_price);
storage.gas_token_address.initialize(gas_token_address);

let actual_gas_token_address = if is_empty(gas_token_address) {
CANONICAL_GAS_TOKEN_ADDRESS
} else {
gas_token_address
};
storage.gas_token_address.initialize(actual_gas_token_address);
}

#[aztec(public)]
Expand Down
18 changes: 13 additions & 5 deletions noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
mod interfaces;

contract FPC {
use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress};
use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::is_empty};
use dep::aztec::state_vars::SharedImmutable;

use crate::interfaces::Token;

struct Storage {
other_asset: SharedImmutable<AztecAddress>,
fee_asset: SharedImmutable<AztecAddress>,
gas_token_address: SharedImmutable<AztecAddress>,
}

global CANONICAL_GAS_TOKEN_ADDRESS = AztecAddress::from_field(0x22a29a9544c0cbdbc35bb21caa78b6f08903be4b390a65319e33afd7443f3499);

#[aztec(public)]
#[aztec(initializer)]
fn constructor(other_asset: AztecAddress, fee_asset: AztecAddress) {
fn constructor(other_asset: AztecAddress, gas_token_address: AztecAddress) {
let actual_gas_token_address = if is_empty(gas_token_address) {
CANONICAL_GAS_TOKEN_ADDRESS
} else {
gas_token_address
};

storage.other_asset.initialize(other_asset);
storage.fee_asset.initialize(fee_asset);
storage.gas_token_address.initialize(actual_gas_token_address);
}

#[aztec(private)]
Expand Down Expand Up @@ -62,7 +70,7 @@ contract FPC {
#[aztec(internal)]
fn pay_fee(refund_address: AztecAddress, amount: Field, asset: AztecAddress) {
let refund = context.call_public_function(
storage.fee_asset.read_public(),
storage.gas_token_address.read_public(),
FunctionSelector::from_signature("pay_fee(Field)"),
[amount]
)[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@ exports[`ContractClass creates a contract class from a contract compilation arti
"privateFunctions": [
{
"selector": {
"value": 2432309179
"value": 283286945
},
"vkHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"isInternal": false
},
{
"selector": {
"value": 283286945
"value": 332459554
},
"vkHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"isInternal": false
},
{
"selector": {
"value": 332459554
"value": 2432309179
},
"vkHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"isInternal": false
Expand Down
24 changes: 16 additions & 8 deletions yarn-project/circuits.js/src/contract/contract_class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { packBytecode } from './public_bytecode.js';
/** Contract artifact including its artifact hash */
type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr };

const cmpFunctionArtifacts = <T extends { selector: FunctionSelector }>(a: T, b: T) =>
a.selector.toField().cmp(b.selector.toField());

/** Creates a ContractClass from a contract compilation artifact. */
export function getContractClassFromArtifact(
artifact: ContractArtifact | ContractArtifactWithHash,
Expand All @@ -20,21 +23,26 @@ export function getContractClassFromArtifact(
selector: FunctionSelector.fromNameAndParameters(f.name, f.parameters),
bytecode: Buffer.from(f.bytecode, 'base64'),
isInternal: f.isInternal,
}));
}))
.sort(cmpFunctionArtifacts);

const packedBytecode = packBytecode(publicFunctions);

const privateFunctions: ContractClass['privateFunctions'] = artifact.functions
.filter(f => f.functionType === FunctionType.SECRET)
.map(f => ({
selector: FunctionSelector.fromNameAndParameters(f.name, f.parameters),
vkHash: getVerificationKeyHash(f.verificationKey!),
isInternal: f.isInternal,
}))
.sort(cmpFunctionArtifacts);

const contractClass: ContractClass = {
version: 1,
artifactHash,
publicFunctions,
packedBytecode,
privateFunctions: artifact.functions
.filter(f => f.functionType === FunctionType.SECRET)
.map(f => ({
selector: FunctionSelector.fromNameAndParameters(f.name, f.parameters),
vkHash: getVerificationKeyHash(f.verificationKey!),
isInternal: f.isInternal,
})),
privateFunctions,
};
return { ...contractClass, ...computeContractClassIdWithPreimage(contractClass) };
}
Expand Down
5 changes: 2 additions & 3 deletions yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('e2e_dapp_subscription', () => {
bananaCoin = await BananaCoin.deploy(aliceWallet, aliceAddress, TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS)
.send()
.deployed();
bananaFPC = await FPCContract.deploy(aliceWallet, bananaCoin.address, gasTokenContract.address).send().deployed();
bananaFPC = await FPCContract.deploy(aliceWallet, bananaCoin.address, AztecAddress.ZERO).send().deployed();

counterContract = await CounterContract.deploy(bobWallet, 0, bobAddress).send().deployed();

Expand All @@ -95,8 +95,7 @@ describe('e2e_dapp_subscription', () => {
// anyone can purchase a subscription for 100 test tokens
bananaCoin.address,
SUBSCRIPTION_AMOUNT,
// I had to pass this in because the address kept changing
gasTokenContract.address,
AztecAddress.ZERO,
)
.send()
.deployed();
Expand Down
14 changes: 14 additions & 0 deletions yarn-project/foundation/src/fields/fields.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,18 @@ describe('Bn254 arithmetic', () => {
expect(() => a.div(b)).toThrowError();
});
});

describe('Comparison', () => {
it.each([
[new Fr(5), new Fr(10), -1],
[new Fr(10), new Fr(5), 1],
[new Fr(5), new Fr(5), 0],
[new Fr(0), new Fr(Fr.MODULUS - 1n), -1],
[new Fr(Fr.MODULUS - 1n), new Fr(0), 1],
[Fr.ZERO, Fr.ZERO, 0],
[Fr.zero(), Fr.ZERO, 0],
])('Should compare field elements correctly', (a, b, expected) => {
expect(a.cmp(b)).toEqual(expected);
});
});
});
6 changes: 6 additions & 0 deletions yarn-project/foundation/src/fields/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ abstract class BaseField {
return this.toBigInt() < rhs.toBigInt();
}

cmp(rhs: BaseField): -1 | 0 | 1 {
const lhsBigInt = this.toBigInt();
const rhsBigInt = rhs.toBigInt();
return lhsBigInt === rhsBigInt ? 0 : lhsBigInt < rhsBigInt ? -1 : 1;
}

isZero(): boolean {
return this.toBuffer().equals(ZERO_BUFFER);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { ClassRegistererAddress, getCanonicalClassRegisterer } from './index.js'
describe('ClassRegisterer', () => {
it('returns canonical protocol contract', () => {
const contract = getCanonicalClassRegisterer();
contract.contractClass.privateFunctions.sort((a, b) => a.selector.value - b.selector.value);
contract.contractClass.publicFunctions.sort((a, b) => a.selector.value - b.selector.value);
expect(omit(contract, 'artifact')).toMatchSnapshot();
expect(contract.address.toString()).toEqual(ClassRegistererAddress.toString());
});
Expand Down
Loading

0 comments on commit b2af880

Please sign in to comment.