forked from NethermindEth/StarknetByExample
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontract.cairo
More file actions
108 lines (90 loc) · 3.89 KB
/
contract.cairo
File metadata and controls
108 lines (90 loc) · 3.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use starknet::ContractAddress;
#[starknet::interface]
trait IZkERC20Token<TContractState> {
fn mint_with_proof(ref self: TContractState, full_proof: Span<felt252>);
fn has_user_minted(self: @TContractState, address: ContractAddress) -> bool;
}
#[starknet::interface]
trait IGroth16VerifierBN254<TContractState> {
fn verify_groth16_proof_bn254(
self: @TContractState, full_proof_with_hints: Span<felt252>,
) -> Option<Span<u256>>;
}
mod errors {
pub const ALREADY_MINTED: felt252 = 'User has already minted tokens';
pub const PROOF_NOT_VERIFIED: felt252 = 'Proof is not correct';
pub const PROOF_ALREADY_USED: felt252 = 'Generate a proof unique to you';
}
#[starknet::contract]
pub mod ZkERC20Token {
use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
use starknet::{ContractAddress, get_caller_address};
use super::{errors, IGroth16VerifierBN254Dispatcher, IGroth16VerifierBN254DispatcherTrait};
use starknet::storage::{
Map, StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry,
};
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
#[abi(embed_v0)]
impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl<ContractState>;
impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;
const MINT_WITH_PROOF_TOKEN_REWARD: u8 = 100;
// used in the front end to generate the proof
const PASSWORD_HASH: u256 =
16260938803047823847354854419633652218467975114284208787981985448019235110758;
#[storage]
struct Storage {
#[substorage(v0)]
erc20: ERC20Component::Storage,
verifier_contract: IGroth16VerifierBN254Dispatcher,
users_who_minted: Map<ContractAddress, bool>,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC20Event: ERC20Component::Event,
}
#[constructor]
fn constructor(
ref self: ContractState,
initial_supply: u256,
recipient: ContractAddress,
name: ByteArray,
symbol: ByteArray,
proof_verifier_address: ContractAddress,
) {
self.erc20.initializer(name, symbol);
self.erc20.mint(recipient, initial_supply);
self
.verifier_contract
.write(IGroth16VerifierBN254Dispatcher { contract_address: proof_verifier_address });
}
#[abi(embed_v0)]
impl ZkERC20TokenImpl of super::IZkERC20Token<ContractState> {
fn mint_with_proof(ref self: ContractState, full_proof: Span<felt252>) {
let caller = get_caller_address();
// Prevent a user from receiving tokens twice
assert(!self.users_who_minted.entry(caller).read(), errors::ALREADY_MINTED);
// Verify the correctness of the proof by calling the verifier contract
// If incorrect, execution of the verifier will fail or return an Option::None
let proof_public_inputs = self
.verifier_contract
.read()
.verify_groth16_proof_bn254(full_proof);
assert(
proof_public_inputs.is_some() && proof_public_inputs.unwrap().len() == 3,
errors::PROOF_NOT_VERIFIED,
);
// Verify the proof has been generated by the user calling this smart contract
let user_address_dec: u256 = *proof_public_inputs.unwrap().at(1);
let address_felt252: felt252 = caller.into();
assert(address_felt252.into() == user_address_dec, errors::PROOF_ALREADY_USED);
// Mint tokens only if the proof is valid and has been generated by the user
self.erc20.mint(caller, MINT_WITH_PROOF_TOKEN_REWARD.into());
self.users_who_minted.entry(caller).write(true);
}
fn has_user_minted(self: @ContractState, address: ContractAddress) -> bool {
self.users_who_minted.entry(address).read()
}
}
}