Skip to content
This repository has been archived by the owner on Jan 15, 2024. It is now read-only.

Commit

Permalink
Move CAPE-specific stuff from Spectrum here. (#387)
Browse files Browse the repository at this point in the history
This eliminates the dependency of this repository on Spectrum.
  • Loading branch information
jbearer authored Jan 26, 2022
1 parent 83353a7 commit 9561bcd
Show file tree
Hide file tree
Showing 20 changed files with 2,160 additions and 520 deletions.
432 changes: 15 additions & 417 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion contracts/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ edition = "2018"
doctest = false

[dependencies]
commit = { git = "ssh://git@github.com/SpectrumXYZ/commit.git", rev = "f48cd52c59755eade0605111826eef3df6abdcf8" }
jf-plonk = { features=["std"], git = "ssh://git@github.com/SpectrumXYZ/jellyfish.git" }
jf-primitives = { features=["std"], git = "ssh://git@github.com/SpectrumXYZ/jellyfish.git" }
jf-aap = { features=["std"], git = "ssh://git@github.com/SpectrumXYZ/jellyfish-apps.git", rev = "eca7b7e85718c7770c24ad90dd34b8a07209a01b" }
jf-rescue = { features=["std"], git = "ssh://git@github.com/SpectrumXYZ/jellyfish.git" }
jf-utils = { features=["std"], git = "ssh://git@github.com/SpectrumXYZ/jellyfish.git" }
key-set = { git = "ssh://git@github.com/SpectrumXYZ/key-set.git" }
reef = { git = "ssh://git@github.com/SpectrumXYZ/reef.git", rev = "6e3e7f683d2f8c1c1da662301f8ca8c2863018cc" }
universal-param = { git = "ssh://git@github.com/SpectrumXYZ/universal-param.git" }
zerok_lib = { git = "ssh://git@github.com/SpectrumXYZ/spectrum.git", rev = "a1af8c87e5f5546b60dbd455dffa20ff211629c8", features = ["mocks"] }
zerok-macros = { git = "ssh://git@github.com/SpectrumXYZ/zerok-macros.git" }
arbitrary = { version="1.0", features=["derive"] }
arbitrary-wrappers = { git = "ssh://git@github.com/SpectrumXYZ/arbitrary-wrappers.git" }
ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" }
itertools = "0.10.1" # needed for jf-aap to compile
ark-std = "0.3.0"
Expand All @@ -41,6 +45,7 @@ async-trait = "0.1.51"
futures = "0.3.16"
strum_macros = "0.20.1"
async-recursion = "1.0.0"
snafu = { version = "0.7", features = ["backtraces"] }

# copied from jellyfish
[dependencies.ark-poly-commit]
Expand Down
5 changes: 2 additions & 3 deletions contracts/rust/src/cape.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![deny(warnings)]
use crate::types as sol;
use crate::{state::CapeTransaction, types as sol};
use anyhow::{anyhow, bail, Result};
use ark_serialize::*;
use ethers::prelude::Address;
Expand All @@ -11,7 +11,6 @@ use jf_aap::transfer::TransferNote;
use jf_aap::TransactionNote;
use num_traits::{FromPrimitive, ToPrimitive};
use std::str::from_utf8;
use zerok_lib::cape_state::CapeTransaction;

pub const DOM_SEP_CAPE_BURN: &[u8] = b"TRICAPE burn";

Expand Down Expand Up @@ -265,14 +264,14 @@ impl From<CAPEConstructorArgs> for (u8, u64) {
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{erc20_asset_description, Erc20Code, EthereumAddr};
use ethers::prelude::{
k256::ecdsa::SigningKey, Http, Provider, SignerMiddleware, Wallet, U256,
};
use jf_aap::structs::{
AssetCode, AssetCodeSeed, AssetDefinition, AssetPolicy, FreezeFlag, RecordOpening,
};
use rand::Rng;
use zerok_lib::cape_state::{erc20_asset_description, Erc20Code, EthereumAddr};

use crate::assertion::Matcher;
use crate::ethereum::{deploy, get_funded_deployer};
Expand Down
18 changes: 9 additions & 9 deletions contracts/rust/src/cape_e2e_tests.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
#![deny(warnings)]

use crate::cape::*;
use crate::ethereum::{deploy, get_funded_deployer};
use crate::types::field_to_u256;
use crate::types::{GenericInto, NullifierSol, TestCAPE};
use crate::universal_param::UNIVERSAL_PARAM;
use crate::{
cape::*,
ethereum::{deploy, get_funded_deployer},
ledger::CapeLedger,
state::{CapeContractState, CapeEthEffect, CapeEvent, CapeOperation, CapeTransaction},
types::field_to_u256,
types::{GenericInto, NullifierSol, TestCAPE},
universal_param::UNIVERSAL_PARAM,
};
use anyhow::Result;
use ethers::prelude::{Address, U256};
use jf_aap::keys::{UserKeyPair, UserPubKey};
Expand All @@ -23,10 +27,6 @@ use rand_chacha::ChaChaRng;
use reef::traits::Ledger as _;
use std::path::Path;
use std::time::Instant;
use zerok_lib::cape_ledger::CapeLedger;
use zerok_lib::cape_state::CapeContractState;
use zerok_lib::cape_state::CapeTransaction;
use zerok_lib::cape_state::{CapeEthEffect, CapeEvent, CapeOperation};

async fn test_2user_maybe_submit(should_submit: bool) -> Result<()> {
let now = Instant::now();
Expand Down
290 changes: 290 additions & 0 deletions contracts/rust/src/ledger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
use crate::state::*;
use arbitrary::{Arbitrary, Unstructured};
use arbitrary_wrappers::*;
use commit::{Commitment, Committable, RawCommitmentBuilder};
use jf_aap::{
keys::{AuditorKeyPair, AuditorPubKey},
structs::{AssetCode, AssetDefinition, Nullifier, RecordCommitment, RecordOpening},
TransactionNote,
};
use reef::{aap, traits::*, AuditError, AuditMemoOpening};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Display;
use std::iter::repeat;
use zerok_macros::ser_test;

// A representation of an unauthenticated sparse set of nullifiers (it is "authenticated" by
// querying the ultimate source of truth, the CAPE smart contract). The HashMap maps any nullifier
// to one of 3 states:
// * Some(true): definitely in the set
// * Some(false): definitely not in the set
// * None: outside the sparse domain of this set, query a full node for a definitive answer
#[ser_test(arbitrary, ark(false))]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct CapeNullifierSet(HashMap<Nullifier, bool>);

impl CapeNullifierSet {
pub fn get(&self, n: Nullifier) -> Option<bool> {
self.0.get(&n).cloned()
}

pub fn insert(&mut self, n: Nullifier, value: bool) {
self.0.insert(n, value);
}
}

impl NullifierSet for CapeNullifierSet {
type Proof = ();

fn multi_insert(&mut self, nullifiers: &[(Nullifier, Self::Proof)]) -> Result<(), Self::Proof> {
for (n, _) in nullifiers {
self.0.insert(*n, true);
}
Ok(())
}
}

impl<'a> Arbitrary<'a> for CapeNullifierSet {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let m: HashMap<ArbitraryNullifier, bool> = u.arbitrary()?;
Ok(Self(m.into_iter().map(|(k, v)| (k.into(), v)).collect()))
}
}

#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, strum_macros::Display)]
pub enum CapeTransactionKind {
AAP(aap::TransactionKind),
Burn,
Wrap,
}

impl TransactionKind for CapeTransactionKind {
fn send() -> Self {
Self::AAP(aap::TransactionKind::send())
}

fn receive() -> Self {
Self::AAP(aap::TransactionKind::receive())
}

fn mint() -> Self {
Self::AAP(aap::TransactionKind::mint())
}

fn freeze() -> Self {
Self::AAP(aap::TransactionKind::freeze())
}

fn unfreeze() -> Self {
Self::AAP(aap::TransactionKind::unfreeze())
}

fn unknown() -> Self {
Self::AAP(aap::TransactionKind::unknown())
}
}

// CapeTransition models all of the objects which can transition a CAPE ledger. This includes
// transactions, submitted from users to the validator via the relayer, as well as ERC20 wrap
// operations, which are submitted directly to the contract but whose outputs end up being included
// in the next committed block.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum CapeTransition {
Transaction(CapeTransaction),
Wrap {
erc20_code: Erc20Code,
src_addr: EthereumAddr,
ro: Box<RecordOpening>,
},
}

impl Committable for CapeTransition {
fn commit(&self) -> Commitment<Self> {
RawCommitmentBuilder::new("CapeTransition")
.var_size_bytes(&bincode::serialize(self).unwrap())
.finalize()
}
}

impl Transaction for CapeTransition {
type NullifierSet = CapeNullifierSet;
type Hash = Commitment<Self>;
type Kind = CapeTransactionKind;

fn aap(note: TransactionNote, _proofs: Vec<()>) -> Self {
Self::Transaction(CapeTransaction::AAP(note))
}

fn open_audit_memo(
&self,
assets: &HashMap<AssetCode, AssetDefinition>,
keys: &HashMap<AuditorPubKey, AuditorKeyPair>,
) -> Result<AuditMemoOpening, AuditError> {
match self {
Self::Transaction(CapeTransaction::AAP(note)) => note.open_audit_memo(assets, keys),
Self::Transaction(CapeTransaction::Burn { xfr, .. }) => {
aap::open_xfr_audit_memo(assets, keys, xfr)
}
_ => Err(AuditError::NoAuditMemos),
}
}

fn proven_nullifiers(&self) -> Vec<(Nullifier, ())> {
let nullifiers = match self {
Self::Transaction(txn) => txn.nullifiers(),
Self::Wrap { .. } => Vec::new(),
};
nullifiers.into_iter().zip(repeat(())).collect()
}

fn output_commitments(&self) -> Vec<RecordCommitment> {
match self {
Self::Transaction(txn) => txn.commitments(),
Self::Wrap { ro, .. } => vec![RecordCommitment::from(&**ro)],
}
}

fn output_openings(&self) -> Option<Vec<RecordOpening>> {
match self {
Self::Wrap { ro, .. } => Some(vec![(**ro).clone()]),
_ => None,
}
}

fn hash(&self) -> Self::Hash {
self.commit()
}

fn kind(&self) -> CapeTransactionKind {
match self {
Self::Transaction(CapeTransaction::AAP(txn)) => match txn {
TransactionNote::Transfer(..) => CapeTransactionKind::send(),
TransactionNote::Mint(..) => CapeTransactionKind::mint(),
TransactionNote::Freeze(..) => CapeTransactionKind::freeze(),
},
Self::Transaction(CapeTransaction::Burn { .. }) => CapeTransactionKind::Burn,
Self::Wrap { .. } => CapeTransactionKind::Wrap,
}
}

fn set_proofs(&mut self, _proofs: Vec<()>) {}
}

impl ValidationError for CapeValidationError {
fn new(msg: impl Display) -> Self {
Self::Failed {
msg: msg.to_string(),
}
}

fn is_bad_nullifier_proof(&self) -> bool {
// CAPE doesn't have nullifier proofs, so validation never fails due to a bad one.
false
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CapeBlock(Vec<CapeTransition>);

impl Committable for CapeBlock {
fn commit(&self) -> Commitment<Self> {
RawCommitmentBuilder::new("CapeBlock")
.array_field(
"txns",
&self.0.iter().map(|x| x.commit()).collect::<Vec<_>>(),
)
.finalize()
}
}

impl Block for CapeBlock {
type Transaction = CapeTransition;
type Error = CapeValidationError;

fn new(txns: Vec<CapeTransition>) -> Self {
Self(txns)
}

fn txns(&self) -> Vec<CapeTransition> {
self.0.clone()
}

fn add_transaction(&mut self, txn: CapeTransition) -> Result<(), CapeValidationError> {
self.0.push(txn);
Ok(())
}
}

// In CAPE, we don't do local lightweight validation to check the results of queries. We trust the
// results of Ethereum query services, and our local validator stores just enough information to
// satisfy the Validator interface required by the wallet. Thus, the CAPE integration for the
// Validator interface is actually more Truster than Validator.
#[ser_test(arbitrary, ark(false))]
#[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CapeTruster {
// The current timestamp. The only requirement is that this is a monotonically increasing value,
// but in this implementation it tracks the number of blocks committed.
now: u64,
// Number of records, for generating new UIDs.
num_records: u64,
}

impl CapeTruster {
pub fn new(now: u64, num_records: u64) -> Self {
Self { now, num_records }
}
}

impl Validator for CapeTruster {
type StateCommitment = u64;
type Block = CapeBlock;

fn now(&self) -> u64 {
self.now
}

fn commit(&self) -> Self::StateCommitment {
// Our commitment is just the block height of the ledger. Since we are trusting a query
// service anyways, this can be used to determine a unique ledger state by querying for the
// state of the ledger at this block index.
self.now
}

fn validate_and_apply(&mut self, block: Self::Block) -> Result<Vec<u64>, CapeValidationError> {
// We don't actually do validation here, since in this implementation we trust the query
// service to provide only valid blocks. Instead, just compute the UIDs of the new records
// assuming the block successfully validates.
let mut uids = vec![];
let mut uid = self.num_records;
for txn in block.0 {
for _ in 0..txn.output_len() {
uids.push(uid);
uid += 1;
}
}
self.num_records = uid;
self.now += 1;

Ok(uids)
}
}

#[derive(Clone, Copy, Debug, Default)]
pub struct CapeLedger;

impl Ledger for CapeLedger {
type Validator = CapeTruster;

fn name() -> String {
String::from("CAPE")
}

fn record_root_history() -> usize {
CapeContractState::RECORD_ROOT_HISTORY_SIZE
}

fn merkle_height() -> u8 {
CAPE_MERKLE_HEIGHT
}
}
2 changes: 2 additions & 0 deletions contracts/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ pub mod cape;
mod cape_e2e_tests;
pub mod ethereum;
pub mod helpers;
pub mod ledger;
mod records_merkle_tree;
mod root_store;
pub mod state;
mod transcript;
pub mod types;
pub mod universal_param;
Loading

0 comments on commit 9561bcd

Please sign in to comment.