Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: crowdfunding contract #4917

Merged
merged 61 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
b207d7a
feat: first version of crowdfunding
sirasistant Feb 19, 2024
70a938b
add to toml
sirasistant Feb 19, 2024
b9b2f77
Initial tests
sklppy88 Feb 19, 2024
73d68a7
start breaking stuffs
sirasistant Feb 19, 2024
2d6d07c
try to fix authwit
sirasistant Feb 19, 2024
d5c2ab7
fix: test
sirasistant Feb 20, 2024
79d7326
Fixing tests
sklppy88 Feb 20, 2024
251aa4e
add
sklppy88 Feb 20, 2024
94c6fa7
chore: refactor a bit test
sirasistant Feb 20, 2024
5b9349f
Working claim
sklppy88 Feb 21, 2024
5e6655f
merge
sklppy88 Feb 21, 2024
261228f
Merge remote-tracking branch 'origin/aztec_juicebox' into aztec_juicebox
sklppy88 Feb 21, 2024
c7283dc
asdf
sklppy88 Feb 21, 2024
5879412
Adding note validity
sklppy88 Feb 21, 2024
b76cc1e
Merge branch 'master' into aztec_juicebox
sklppy88 Feb 22, 2024
ec90172
allow pxe http client retries
spypsy Feb 22, 2024
64f519a
Merge branch 'master' into aztec_juicebox
sklppy88 Feb 22, 2024
d5463f9
Merge remote-tracking branch 'origin/aztec_juicebox' into aztec_juicebox
sklppy88 Feb 22, 2024
6df28df
feat: added event
sirasistant Feb 23, 2024
e317951
Merge branch 'master' into aztec_juicebox
benesjan Mar 5, 2024
e6fcbe4
fmt
benesjan Mar 5, 2024
74a278d
fixes
benesjan Mar 5, 2024
dd3ad12
no juicebox
benesjan Mar 5, 2024
5ad9915
enabling test in CI
benesjan Mar 5, 2024
bf3910f
fmt
benesjan Mar 5, 2024
1975611
WIP
benesjan Mar 5, 2024
18b6d8d
adding a TODO
benesjan Mar 5, 2024
36cac5e
Merge branch 'master' into aztec_juicebox
benesjan Mar 5, 2024
1b7e2f0
fix
benesjan Mar 5, 2024
36669db
diff cleanup
benesjan Mar 5, 2024
f58313d
adding TODO
benesjan Mar 5, 2024
dd0078f
Merge branch 'master' into aztec_juicebox
benesjan Mar 5, 2024
c29dbbb
Address comments
sklppy88 Mar 5, 2024
ee68d37
Addressing comments pt 2
sklppy88 Mar 5, 2024
4ba8385
Apply suggestions from code review
benesjan Mar 6, 2024
7e86486
import cleanup
benesjan Mar 6, 2024
16c7be5
Merge branch 'master' into aztec_juicebox
benesjan Mar 6, 2024
b60f5f6
using SharedImmutable in Claim contract + checking note contract
benesjan Mar 6, 2024
55a1ad6
Using SharedImmutable in Crowdfunding contract
benesjan Mar 6, 2024
e85a7a0
Merge branch 'master' into aztec_juicebox
benesjan Mar 6, 2024
4304d88
cleanup
benesjan Mar 6, 2024
864a9b3
fixes
benesjan Mar 6, 2024
1d8845d
naming cleanup
benesjan Mar 6, 2024
f1fc8c1
cleanup
benesjan Mar 6, 2024
d5ea55a
better test name
benesjan Mar 6, 2024
5010252
Merge branch 'master' into aztec_juicebox
benesjan Mar 6, 2024
bb3146d
final final cleanup
benesjan Mar 6, 2024
f9d2693
more negative tests
benesjan Mar 6, 2024
bd92b5b
deadline
benesjan Mar 6, 2024
891d309
Merge branch 'master' into aztec_juicebox
benesjan Mar 6, 2024
c03209c
fix
benesjan Mar 6, 2024
8a21aa2
test speedup
benesjan Mar 6, 2024
53a0a85
adding TODO
benesjan Mar 6, 2024
14dccd5
deadline test
benesjan Mar 6, 2024
0fa7c45
fix
benesjan Mar 6, 2024
b08bb0d
Merge branch 'master' into aztec_juicebox
benesjan Mar 6, 2024
6d56e9f
Update .circleci/config.yml
benesjan Mar 7, 2024
51d6fdd
Merge branch 'master' into aztec_juicebox
benesjan Mar 7, 2024
fa743bd
interface cleanup
benesjan Mar 7, 2024
790cff0
cleanup
benesjan Mar 7, 2024
3e47da7
Merge branch 'master' into aztec_juicebox
benesjan Mar 7, 2024
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
14 changes: 14 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,18 @@ jobs:
aztec_manifest_key: end-to-end
<<: *defaults_e2e_test

e2e-crowdfunding-and-claim:
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_crowdfunding_and_claim.test.ts
aztec_manifest_key: end-to-end

e2e-public-cross-chain-messaging:
steps:
- *checkout
Expand Down Expand Up @@ -1377,6 +1389,7 @@ workflows:
- e2e-multiple-accounts-1-enc-key: *e2e_test
- e2e-cli: *e2e_test
- e2e-cross-chain-messaging: *e2e_test
- e2e-crowdfunding-and-claim: *e2e_test
- e2e-public-cross-chain-messaging: *e2e_test
- e2e-public-to-private-messaging: *e2e_test
- e2e-account-contracts: *e2e_test
Expand Down Expand Up @@ -1440,6 +1453,7 @@ workflows:
- e2e-multiple-accounts-1-enc-key
- e2e-cli
- e2e-cross-chain-messaging
- e2e-crowdfunding-and-claim
- e2e-public-cross-chain-messaging
- e2e-public-to-private-messaging
- e2e-account-contracts
Expand Down
8 changes: 7 additions & 1 deletion noir-projects/aztec-nr/aztec/src/note/note_header.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use dep::protocol_types::address::AztecAddress;
use dep::protocol_types::traits::Empty;
use dep::protocol_types::traits::{Empty, Serialize};

struct NoteHeader {
contract_address: AztecAddress,
Expand All @@ -21,3 +21,9 @@ impl NoteHeader {
NoteHeader { contract_address, nonce, storage_slot, is_transient: false }
}
}

impl Serialize<4> for NoteHeader {
fn serialize(self) -> [Field; 4] {
[self.contract_address.to_field(), self.nonce, self.storage_slot, self.is_transient as Field]
}
}
8 changes: 8 additions & 0 deletions noir-projects/aztec-nr/value-note/src/value_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,11 @@ impl ValueNote {
ValueNote { value, owner, randomness, header }
}
}

impl Serialize<7> for ValueNote {
fn serialize(self) -> [Field; 7] {
let header = self.header.serialize();

[self.value, self.owner.to_field(), self.randomness, header[0], header[1], header[2], header[3]]
}
}
2 changes: 2 additions & 0 deletions noir-projects/noir-contracts/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ members = [
"contracts/benchmarking_contract",
"contracts/card_game_contract",
"contracts/child_contract",
"contracts/claim_contract",
"contracts/contract_class_registerer_contract",
"contracts/contract_instance_deployer_contract",
"contracts/counter_contract",
"contracts/crowdfunding_contract",
"contracts/delegator_contract",
"contracts/delegated_on_contract",
"contracts/docs_example_contract",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "claim_contract"
authors = [""]
compiler_version = ">=0.18.0"
type = "contract"

[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
value_note = { path = "../../../aztec-nr/value-note" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use dep::aztec::{
protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress},
context::PrivateContext,
};

struct Token {
address: AztecAddress,
}

impl Token {
pub fn at(address: AztecAddress) -> Self {
Self { address }
}

fn mint_public(self: Self, context: &mut PrivateContext, to: AztecAddress, amount: Field) {
let _ret = context.call_public_function(
self.address,
FunctionSelector::from_signature("mint_public((Field),Field)"),
[to.to_field(), amount]
);
}

pub fn transfer(
self: Self,
context: &mut PrivateContext,
from: AztecAddress,
to: AztecAddress,
amount: Field,
nonce: Field
) {
let _ret = context.call_private_function(
self.address,
FunctionSelector::from_signature("transfer((Field),(Field),Field,Field)"),
[from.to_field(), to.to_field(), amount, nonce]
);
}
}
59 changes: 59 additions & 0 deletions noir-projects/noir-contracts/contracts/claim_contract/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
contract Claim {
mod interfaces;

use dep::aztec::{
history::note_inclusion::prove_note_inclusion,
protocol_types::{
abis::function_selector::FunctionSelector,
address::AztecAddress,
},
state_vars::SharedImmutable,
};
use dep::value_note::value_note::ValueNote;
use interfaces::Token;

struct Storage {
// Address of a contract based on whose notes we distribute the rewards
target_contract: SharedImmutable<AztecAddress>,
// Token to be distributed as a reward when claiming
reward_token: SharedImmutable<AztecAddress>,
}

#[aztec(private)]
fn constructor(target_contract: AztecAddress, reward_token: AztecAddress) {
let selector = FunctionSelector::from_signature("_initialize((Field),(Field))");
context.call_public_function(
context.this_address(),
selector,
[target_contract.to_field(), reward_token.to_field()]
);
}

#[aztec(public)]
#[aztec(internal)]
#[aztec(noinitcheck)]
fn _initialize(target_contract: AztecAddress, reward_token: AztecAddress) {
storage.target_contract.initialize(target_contract);
storage.reward_token.initialize(reward_token);
}

#[aztec(private)]
fn claim(proof_note: ValueNote) {
benesjan marked this conversation as resolved.
Show resolved Hide resolved
// 1) Check that the note corresponds to the target contract
let target_address = storage.target_contract.read_private();
assert(target_address == proof_note.header.contract_address, "Note does not correspond to the target contract");

// 2) Prove that the note hash exists in the note hash tree
prove_note_inclusion(proof_note, context);

// 3) Compute and emit a nullifier which is unique to the note and this contract to ensure the reward can be
// claimed only once with the given note.
// Note: The nullifier is unique to the note and THIS contract because the protocol siloes all nullifiers with
// the address of a contract it was emitted from.
context.push_new_nullifier(proof_note.compute_nullifier(&mut context), 0);

// 4) Finally we mint the reward token to the sender of the transaction
let reward_token = Token::at(storage.reward_token.read_private());
reward_token.mint_public(&mut context, context.msg_sender(), proof_note.value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "crowdfunding_contract"
authors = [""]
compiler_version = ">=0.18.0"
type = "contract"

[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
value_note = { path = "../../../aztec-nr/value-note" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}};
use dep::aztec::{context::{PrivateContext, PublicContext}};

struct Token {
nventuro marked this conversation as resolved.
Show resolved Hide resolved
address: AztecAddress,
}

impl Token {
pub fn at(address: AztecAddress) -> Self {
Self { address }
}

pub fn transfer(
self: Self,
context: &mut PrivateContext,
from: AztecAddress,
to: AztecAddress,
amount: Field,
nonce: Field
) {
let _ret = context.call_private_function(
self.address,
FunctionSelector::from_signature("transfer((Field),(Field),Field,Field)"),
[from.to_field(), to.to_field(), amount, nonce]
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
contract Crowdfunding {
mod interfaces;

use dep::aztec::{
log::emit_unencrypted_log_from_private,
protocol_types::{
abis::function_selector::FunctionSelector,
address::AztecAddress,
traits::Serialize
},
state_vars::{PrivateSet, PublicImmutable, SharedImmutable},
};
use dep::value_note::value_note::ValueNote;
use interfaces::Token;

#[event]
struct WithdrawalProcessed {
who: AztecAddress,
amount: u64,
}

impl Serialize<2> for WithdrawalProcessed {
fn serialize(self: Self) -> [Field; 2] {
[self.who.to_field(), self.amount as Field]
}
}

struct Storage {
// Token used for donations (e.g. DAI)
donation_token: SharedImmutable<AztecAddress>,
// Crowdfunding campaign operator
operator: SharedImmutable<AztecAddress>,
// End of the crowdfunding campaign after which no more donations are accepted
// TODO(#4990): Make deadline a u64 once the neccessary traits are implemented
deadline: PublicImmutable<Field>,
// Notes emitted to donors when they donate (later on used to claim rewards in the Claim contract)
claim_notes: PrivateSet<ValueNote>,
}

#[aztec(private)]
fn constructor(donation_token: AztecAddress, operator: AztecAddress, deadline: u64) {
let selector = FunctionSelector::from_signature("_initialize((Field),(Field),Field)");
context.call_public_function(
context.this_address(),
selector,
[donation_token.to_field(), operator.to_field(), deadline as Field]
);
}

#[aztec(public)]
#[aztec(internal)]
#[aztec(noinitcheck)]
// TODO(#4990): Make deadline a u64 once the neccessary traits are implemented
fn _initialize(donation_token: AztecAddress, operator: AztecAddress, deadline: Field) {
storage.donation_token.initialize(donation_token);
storage.operator.initialize(operator);
storage.deadline.initialize(deadline);
}

#[aztec(public)]
#[aztec(internal)]
fn _check_deadline() {
// TODO(#4990): Remove the cast here once u64 is used directly
let deadline = storage.deadline.read() as u64;
assert(context.timestamp() as u64 < deadline, "Deadline has passed");
}

#[aztec(private)]
fn donate(amount: u64) {
// 1) Check that the deadline has not passed
context.call_public_function(
context.this_address(),
FunctionSelector::from_signature("_check_deadline()"),
[]
);

// 2) Transfer the donation tokens from donor to this contract
let donation_token = Token::at(storage.donation_token.read_private());
donation_token.transfer(
&mut context,
context.msg_sender(),
context.this_address(),
amount as Field,
0
);

// 3) Create a value note for the donor so that he can later on claim a rewards token in the Claim
// contract by proving that the hash of this note exists in the note hash tree.
let mut note = ValueNote::new(amount as Field, context.msg_sender());
storage.claim_notes.insert(&mut note, true);
}

// Withdraws balance to the operator. Requires that msg_sender() is the operator.
#[aztec(private)]
fn withdraw(amount: u64) {
// 1) Check that msg_sender() is the operator
let operator_address = storage.operator.read_private();
assert(context.msg_sender() == operator_address, "Not an operator");

// 2) Transfer the donation tokens from this contract to the operator
let donation_token = Token::at(storage.donation_token.read_private());
donation_token.transfer(&mut context, context.this_address(), operator_address, amount as Field, 0);

// 3) Emit an unencrypted event so that anyone can audit how much the operator has withdrawn
let event = WithdrawalProcessed { amount, who: operator_address };
emit_unencrypted_log_from_private(&mut context, event.serialize());
}
}
4 changes: 4 additions & 0 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ export abstract class BaseWallet implements Wallet {
getNotes(filter: NoteFilter): Promise<ExtendedNote[]> {
return this.pxe.getNotes(filter);
}
// TODO(#4956): Un-expose this
getNoteNonces(note: ExtendedNote): Promise<Fr[]> {
return this.pxe.getNoteNonces(note);
}
getPublicStorageAt(contract: AztecAddress, storageSlot: Fr): Promise<any> {
return this.pxe.getPublicStorageAt(contract, storageSlot);
}
Expand Down
9 changes: 9 additions & 0 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,15 @@ export interface PXE {
*/
getNotes(filter: NoteFilter): Promise<ExtendedNote[]>;

/**
* Finds the nonce(s) for a given note.
* @param note - The note to find the nonces for.
* @returns The nonces of the note.
* @remarks More than a single nonce may be returned since there might be more than one nonce for a given note.
* TODO(#4956): Un-expose this
*/
getNoteNonces(note: ExtendedNote): Promise<Fr[]>;

/**
* Adds a note to the database.
* @throws If the note hash of the note doesn't exist in the tree.
Expand Down
2 changes: 2 additions & 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 @@ -100,9 +100,11 @@ AppSubscriptionContractArtifact
BenchmarkingContractArtifact
CardGameContractArtifact
ChildContractArtifact
ClaimContractArtifact
ContractClassRegistererContractArtifact
ContractInstanceDeployerContractArtifact
CounterContractArtifact
CrowdfundingContractArtifact
DelegatedOnContractArtifact
DelegatorContractArtifact
DocsExampleContractArtifact
Expand Down
Loading
Loading