Skip to content

Commit

Permalink
feat: add str support for args + add name/symbol/decimal to token (Az…
Browse files Browse the repository at this point in the history
…tecProtocol#3862)

Fixes AztecProtocol#2889 by adding support for str as input arg and adding a compressed string struct.
  • Loading branch information
LHerskind authored Jan 9, 2024
1 parent 5a329f4 commit 0bf5d8c
Show file tree
Hide file tree
Showing 37 changed files with 503 additions and 43 deletions.
31 changes: 30 additions & 1 deletion noir/aztec_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,21 @@ fn create_context(ty: &str, params: &[Param]) -> Vec<Statement> {
UnresolvedTypeData::Integer(..) | UnresolvedTypeData::Bool => {
add_cast_to_hasher(identifier)
}
_ => unreachable!("[Aztec Noir] Provided parameter type is not supported"),
UnresolvedTypeData::String(..) => {
let (var_bytes, id) = str_to_bytes(identifier);
injected_expressions.push(var_bytes);
add_array_to_hasher(
&id,
&UnresolvedType {
typ: UnresolvedTypeData::Integer(Signedness::Unsigned, 32),
span: None,
},
)
}
_ => panic!(
"[Aztec Noir] Provided parameter type: {:?} is not supported",
unresolved_type
),
};
injected_expressions.push(expression);
}
Expand Down Expand Up @@ -909,6 +923,21 @@ fn add_struct_to_hasher(identifier: &Ident) -> Statement {
)))
}

fn str_to_bytes(identifier: &Ident) -> (Statement, Ident) {
// let identifier_as_bytes = identifier.as_bytes();
let var = variable_ident(identifier.clone());
let contents = if let ExpressionKind::Variable(p) = &var.kind {
p.segments.first().cloned().unwrap_or_else(|| panic!("No segments")).0.contents
} else {
panic!("Unexpected identifier type")
};
let bytes_name = format!("{}_bytes", contents);
let var_bytes = assignment(&bytes_name, method_call(var, "as_bytes", vec![]));
let id = Ident::new(bytes_name, Span::default());

(var_bytes, id)
}

fn create_loop_over(var: Expression, loop_body: Vec<Statement>) -> Statement {
// If this is an array of primitive types (integers / fields) we can add them each to the hasher
// casted to a field
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ ecdsa_secp256k1_verify(
signature): boolean
```

Calculates the Blake2s256 hash of the input bytes and represents these as a single field element.
Verifies a ECDSA signature over the secp256k1 curve.

## Parameters
Expand Down
2 changes: 1 addition & 1 deletion noir/docs/docs/reference/NoirJS/noir_js/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
| :------ | :------ |
| [and](functions/and.md) | Performs a bitwise AND operation between `lhs` and `rhs` |
| [blake2s256](functions/blake2s256.md) | Calculates the Blake2s256 hash of the input bytes |
| [ecdsa\_secp256k1\_verify](functions/ecdsa_secp256k1_verify.md) | Calculates the Blake2s256 hash of the input bytes and represents these as a single field element. |
| [ecdsa\_secp256k1\_verify](functions/ecdsa_secp256k1_verify.md) | Verifies a ECDSA signature over the secp256k1 curve. |
| [ecdsa\_secp256r1\_verify](functions/ecdsa_secp256r1_verify.md) | Verifies a ECDSA signature over the secp256r1 curve. |
| [keccak256](functions/keccak256.md) | Calculates the Keccak256 hash of the input bytes |
| [sha256](functions/sha256.md) | Calculates the SHA256 hash of the input bytes |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod bool_serialization;
mod field_serialization;
mod u8_serialization;
mod u32_serialization;
mod address_serialization;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::types::type_serialization::TypeSerializationInterface;

global U8_SERIALIZED_LEN: Field = 1;

fn deserializeU8(fields: [Field; U8_SERIALIZED_LEN]) -> u8 {
fields[0] as u8
}

fn serializeU8(value: u8) -> [Field; U8_SERIALIZED_LEN] {
[value as Field]
}

global U8SerializationMethods = TypeSerializationInterface {
deserialize: deserializeU8,
serialize: serializeU8,
};
9 changes: 9 additions & 0 deletions yarn-project/aztec-nr/compressed-string/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "compressed_string"
authors = [""]
compiler_version = ">=0.18.0"
type = "lib"

[dependencies]
aztec = {path = "../aztec"}
protocol_types = {path = "../../noir-protocol-circuits/src/crates/types"}
142 changes: 142 additions & 0 deletions yarn-project/aztec-nr/compressed-string/src/compressed_string.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use dep::aztec::types::type_serialization::TypeSerializationInterface;
use dep::protocol_types::utils::field::field_from_bytes;
use dep::std;

// A Fixedsize Compressed String.
// Essentially a special version of Compressed String for practical use.
struct FieldCompressedString{
value: Field
}

impl FieldCompressedString{
pub fn is_eq(self, other: FieldCompressedString) -> bool {
self.value == other.value
}

pub fn from_field(input_field: Field) -> Self {
Self {value: input_field}
}

pub fn from_string(input_string: str<31>) -> Self {
Self {value: field_from_bytes(input_string.as_bytes(), true)}
}

pub fn to_bytes(self) -> [u8; 31] {
let mut result = [0; 31];
let bytes = self.value.to_be_bytes(31);
for i in 0..31 {
result[i] = bytes[i];
}
result
}

pub fn serialize(self) -> [Field; 1] {
[self.value]
}

pub fn deserialize(input: [Field; 1]) -> Self {
Self { value: input[0] }
}
}

fn deserialize(fields: [Field; 1]) -> FieldCompressedString {
FieldCompressedString { value: fields[0] }
}

fn serialize(value: FieldCompressedString) -> [Field; 1] {
value.serialize()
}
global FieldCompressedStringSerializationMethods = TypeSerializationInterface {
deserialize,
serialize,
};

// The general Compressed String.
// Compresses M bytes into N fields.
// Can be used for longer strings that don't fit in a single field.
// Each field can store 31 characters, so N should be M/31 rounded up.
struct CompressedString<N, M> {
value: [Field; N]
}

impl<N, M> CompressedString<N, M> {
pub fn from_string(input_string: str<M>) -> Self {
let mut fields = [0; N];
let byts = input_string.as_bytes();

let mut r_index = 0 as u32;

for i in 0..N {
let mut temp = [0 as u8; 31];
for j in 0..31 {
if r_index < M {
temp[j] = byts[r_index];
r_index += 1;
}
}

fields[i] = field_from_bytes(temp, true);
}

Self { value: fields }
}

pub fn to_bytes(self) -> [u8; M] {
let mut result = [0; M];
let mut w_index = 0 as u32;
for i in 0..N {
let bytes = self.value[i].to_be_bytes(31);
for j in 0..31 {
if w_index < M {
result[w_index] = bytes[j];
w_index += 1;
}
}
}
result
}

pub fn serialize(self) -> [Field; N] {
self.value
}

pub fn deserialize(input: [Field; N]) -> Self {
Self { value: input }
}
}

#[test]
fn test_short_string() {
let i = "Hello world";
let b = i.as_bytes();
let name: CompressedString<1,11> = CompressedString::from_string(i);
let p = b == name.to_bytes();
assert(p, "invalid recover");
}

#[test]
fn test_long_string() {
let i = "Hello world. I'm setting up a very long text of blibbablubb such that we can see if works as planned for longer names.";
let b = i.as_bytes();
let name: CompressedString<4,118> = CompressedString::from_string(i);
let p = b == name.to_bytes();
assert(p, "invalid recover");
}

#[test]
fn test_long_string_work_with_too_many_fields() {
let i = "Hello world. I'm setting up a very long text of blibbablubb such that we can see if works as planned for longer names.";
let b = i.as_bytes();
let name: CompressedString<5,118> = CompressedString::from_string(i);
let p = b == name.to_bytes();
assert(p, "invalid recover");
}

#[test(should_fail)]
fn test_long_string_fail_with_too_few_fields() {
let i = "Hello world. I'm setting up a very long text of blibbablubb such that we can see if works as planned for longer names.";
let b = i.as_bytes();
let name: CompressedString<3,118> = CompressedString::from_string(i);
let p = b == name.to_bytes();
assert(p, "invalid recover");
}
4 changes: 4 additions & 0 deletions yarn-project/aztec-nr/compressed-string/src/lib.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod compressed_string;

use crate::compressed_string::{CompressedString};
use crate::compressed_string::{FieldCompressedString, FieldCompressedStringSerializationMethods};
2 changes: 1 addition & 1 deletion yarn-project/aztec-sandbox/src/examples/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function main() {
logger(`Created Alice and Bob accounts: ${alice.address.toString()}, ${bob.address.toString()}`);

logger('Deploying Token...');
const token = await TokenContract.deploy(aliceWallet, alice).send().deployed();
const token = await TokenContract.deploy(aliceWallet, alice, 'TokenName', 'TokenSymbol', 18).send().deployed();
logger('Token deployed');

// Create the contract abstraction and link it to Alice's and Bob's wallet for future signing
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/cli/src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ function encodeArg(arg: string, abiType: ABIType, name: string): any {
} else {
throw Error(`Invalid boolean value passed for ${name}: ${arg}.`);
}
} else if (kind === 'string') {
return arg;
} else if (kind === 'array') {
let arr;
const res = [];
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/end-to-end/src/cli_docs_sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ LendingContractArtifact
ParentContractArtifact
PendingCommitmentsContractArtifact
PriceFeedContractArtifact
ReaderContractArtifact
SchnorrAccountContractArtifact
SchnorrHardcodedAccountContractArtifact
SchnorrSingleKeyAccountContractArtifact
Expand Down Expand Up @@ -255,7 +256,7 @@ Accounts found:
// Test deploy
docs = `
// docs:start:deploy
% aztec-cli deploy TokenContractArtifact --args $ADDRESS
% aztec-cli deploy TokenContractArtifact --args $ADDRESS TokenName TKN 18
Contract deployed at 0x1ae8eea0dc265fb7f160dae62cc8912686d8a9ed78e821fbdd8bcedc54c06d0f
// docs:end:deploy
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_2_pxes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('e2e_2_pxes', () => {

const deployTokenContract = async (initialAdminBalance: bigint, admin: AztecAddress, pxe: PXE) => {
logger(`Deploying Token contract...`);
const contract = await TokenContract.deploy(walletA, admin).send().deployed();
const contract = await TokenContract.deploy(walletA, admin, 'TokenName', 'TokenSymbol', 18).send().deployed();

if (initialAdminBalance > 0n) {
await mintTokens(contract, admin, initialAdminBalance, pxe);
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_block_building.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('e2e_block_building', () => {

// Deploy a contract in the first transaction
// In the same block, call a public method on the contract
const deployer = TokenContract.deploy(owner, owner.getCompleteAddress());
const deployer = TokenContract.deploy(owner, owner.getCompleteAddress(), 'TokenName', 'TokenSymbol', 18);
await deployer.create();

// We can't use `TokenContract.at` to call a function because it checks the contract is deployed
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_cheat_codes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('e2e_cheat_codes', () => {
rollupAddress = deployL1ContractsValues.l1ContractAddresses.rollupAddress;
admin = accounts[0];

token = await TokenContract.deploy(wallet, admin).send().deployed();
token = await TokenContract.deploy(wallet, admin, 'TokenName', 'TokenSymbol', 18).send().deployed();
}, 100_000);

afterAll(() => teardown());
Expand Down
14 changes: 12 additions & 2 deletions yarn-project/end-to-end/src/e2e_deploy_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,18 @@ describe('e2e_deploy_contract', () => {
try {
// This test requires at least another good transaction to go through in the same block as the bad one.
// I deployed the same contract again but it could really be any valid transaction here.
const goodDeploy = new ContractDeployer(TokenContractArtifact, wallet).deploy(AztecAddress.random());
const badDeploy = new ContractDeployer(TokenContractArtifact, wallet).deploy(AztecAddress.ZERO);
const goodDeploy = new ContractDeployer(TokenContractArtifact, wallet).deploy(
AztecAddress.random(),
'TokenName',
'TKN',
18,
);
const badDeploy = new ContractDeployer(TokenContractArtifact, wallet).deploy(
AztecAddress.ZERO,
'TokenName',
'TKN',
18,
);

await Promise.all([
goodDeploy.simulate({ skipPublicSimulation: true }),
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_escrow_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('e2e_escrow_contract', () => {
logger(`Escrow contract deployed at ${escrowContract.address}`);

// Deploy Token contract and mint funds for the escrow contract
token = await TokenContract.deploy(wallet, owner).send().deployed();
token = await TokenContract.deploy(wallet, owner, 'TokenName', 'TokenSymbol', 18).send().deployed();

const mintAmount = 100n;
const secret = Fr.random();
Expand Down
8 changes: 6 additions & 2 deletions yarn-project/end-to-end/src/e2e_lending_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,18 @@ describe('e2e_lending_contract', () => {

{
logger(`Deploying collateral asset feed contract...`);
const receipt = await waitForSuccess(TokenContract.deploy(wallet, accounts[0]).send());
const receipt = await waitForSuccess(
TokenContract.deploy(wallet, accounts[0], 'TokenName', 'TokenSymbol', 18).send(),
);
logger(`Collateral asset deployed to ${receipt.contractAddress}`);
collateralAsset = await TokenContract.at(receipt.contractAddress!, wallet);
}

{
logger(`Deploying stable coin contract...`);
const receipt = await waitForSuccess(TokenContract.deploy(wallet, accounts[0]).send());
const receipt = await waitForSuccess(
TokenContract.deploy(wallet, accounts[0], 'TokenName', 'TokenSymbol', 18).send(),
);
logger(`Stable coin asset deployed to ${receipt.contractAddress}`);
stableCoin = await TokenContract.at(receipt.contractAddress!, wallet);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('e2e_multiple_accounts_1_enc_key', () => {
}

logger(`Deploying Token...`);
const token = await TokenContract.deploy(wallets[0], accounts[0]).send().deployed();
const token = await TokenContract.deploy(wallets[0], accounts[0], 'TokenName', 'TokenSymbol', 18).send().deployed();
tokenAddress = token.address;
logger(`Token deployed at ${tokenAddress}`);

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/e2e_sandbox_example.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('e2e_sandbox_example', () => {
logger(`Deploying token contract...`);

// Deploy the contract and set Alice as the admin while doing so
const contract = await TokenContract.deploy(aliceWallet, alice).send().deployed();
const contract = await TokenContract.deploy(aliceWallet, alice, 'TokenName', 'TokenSymbol', 18).send().deployed();
logger(`Contract successfully deployed at address ${contract.address.toShortString()}`);

// Create the contract abstraction and link it to Alice's wallet for future signing
Expand Down
Loading

0 comments on commit 0bf5d8c

Please sign in to comment.