diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 2e84b4a..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "shielder/PSP22"] - path = shielder/PSP22 - url = https://github.com/Cardinal-Cryptography/PSP22.git diff --git a/shielder/Makefile b/shielder/Makefile index 6638146..4db3c8a 100644 --- a/shielder/Makefile +++ b/shielder/Makefile @@ -26,19 +26,13 @@ check-drink-tests: ## Run cargo checks on drink tests. .PHONY: check check: check-contract check-mocked-zk check-drink-tests ## Run cargo checks -.PHONY: build-psp22 -build-psp22: ## Builds psp22 contracts. - @echo "Building psp22 contract" ; \ - git submodule update --init ; \ - cargo contract build --quiet --manifest-path PSP22/Cargo.toml --features "contract" --release ; \ - .PHONY: build-shielder build-shielder: ## Builds shielder contracts. @echo "Building shielder contract" ; \ cargo contract build --quiet --manifest-path contract/Cargo.toml --release ; \ .PHONY: setup-tests -setup-tests: build-psp22 build-shielder ## Builds contracts and generates wrappers. +setup-tests: build-shielder ## Builds contracts and generates wrappers. .PHONY: shielder-unit-tests shielder-unit-tests: ## Runs unit tests for contract. diff --git a/shielder/PSP22 b/shielder/PSP22 deleted file mode 160000 index 94a8f73..0000000 --- a/shielder/PSP22 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 94a8f7393afef89da9de97957729c60e5b4f6cad diff --git a/shielder/contract/lib.rs b/shielder/contract/lib.rs index b09021d..69cbf21 100755 --- a/shielder/contract/lib.rs +++ b/shielder/contract/lib.rs @@ -10,6 +10,7 @@ mod types; /// Contract module #[ink::contract] +#[allow(clippy::large_enum_variant)] pub mod contract { use crate::{errors::ShielderError, merkle::MerkleTree, traits::psp22::PSP22Error, types::Set}; @@ -111,6 +112,85 @@ pub mod contract { ) .returns::>() .invoke()?, + OpPub::DepositRelayer { + amount, + token, + user, + fee, + fee_token, + relayer, + } => { + build_call::() + .call(AccountId::from(token.bytes)) + .call_v1() + .gas_limit(0) + .transferred_value(0) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!( + "PSP22::transfer_from" + ))) + .push_arg(AccountId::from(user.bytes)) + .push_arg(self.env().account_id()) + .push_arg(amount) + .push_arg([].to_vec() as ink::prelude::vec::Vec), + ) + .returns::>() + .invoke()?; + build_call::() + .call(AccountId::from(fee_token.bytes)) + .call_v1() + .gas_limit(0) + .transferred_value(0) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!( + "PSP22::transfer" + ))) + .push_arg(AccountId::from(relayer.bytes)) + .push_arg(fee) + .push_arg([].to_vec() as ink::prelude::vec::Vec), + ) + .returns::>() + .invoke()?; + } + OpPub::WithdrawRelayer { + amount, + token, + user, + fee, + fee_token, + relayer, + } => { + build_call::() + .call(AccountId::from(token.bytes)) + .call_v1() + .gas_limit(0) + .transferred_value(0) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!( + "PSP22::transfer" + ))) + .push_arg(AccountId::from(user.bytes)) + .push_arg(amount) + .push_arg([].to_vec() as ink::prelude::vec::Vec), + ) + .returns::>() + .invoke()?; + build_call::() + .call(AccountId::from(fee_token.bytes)) + .call_v1() + .gas_limit(0) + .transferred_value(0) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!( + "PSP22::transfer" + ))) + .push_arg(AccountId::from(relayer.bytes)) + .push_arg(fee) + .push_arg([].to_vec() as ink::prelude::vec::Vec), + ) + .returns::>() + .invoke()?; + } }; Ok(()) } diff --git a/shielder/drink_tests/Cargo.lock b/shielder/drink_tests/Cargo.lock index 9ae10a4..3ef496a 100644 --- a/shielder/drink_tests/Cargo.lock +++ b/shielder/drink_tests/Cargo.lock @@ -1370,6 +1370,7 @@ dependencies = [ "anyhow", "drink", "mocked_zk", + "psp22", "rand", "shielder-contract", ] @@ -3489,6 +3490,14 @@ dependencies = [ "cc", ] +[[package]] +name = "psp22" +version = "0.3.0" +source = "git+https://github.com/Cardinal-Cryptography/PSP22.git?rev=676768b#676768b226d1460be95aff12cf35b3dcc84c7952" +dependencies = [ + "ink", +] + [[package]] name = "quote" version = "1.0.35" diff --git a/shielder/drink_tests/Cargo.toml b/shielder/drink_tests/Cargo.toml index 75fee76..15a5487 100644 --- a/shielder/drink_tests/Cargo.toml +++ b/shielder/drink_tests/Cargo.toml @@ -14,3 +14,4 @@ rand = { version = "0.8.5", default-features = false } anyhow = { version = "1.0.79", default-features = false } mocked_zk = { path = "../mocked_zk", default-features = false } shielder-contract = { path = "../contract", default-features = false, features = ["ink-as-dependency"] } +psp22 = { git = "https://github.com/Cardinal-Cryptography/PSP22.git", rev = "676768b", default-features = false, features = ["ink-as-dependency", "contract"] } diff --git a/shielder/drink_tests/src/lib.rs b/shielder/drink_tests/src/lib.rs index 1c5c9ec..0330933 100644 --- a/shielder/drink_tests/src/lib.rs +++ b/shielder/drink_tests/src/lib.rs @@ -24,13 +24,13 @@ mod tests { session = session.with_actor(alice.clone()); let psp22_address = deploy_test_token(&mut session, 100)?; - let shielder_address = deploy_shielder(&mut session, &psp22_address)?; + let shielder_address = deploy_shielder(&mut session, vec![psp22_address.clone()])?; // CREATE ACCOUNT let user_shielded_data = create_shielder_account( &mut session, &shielder_address, - &psp22_address, + vec![psp22_address.clone()], rng.gen::().into(), )?; @@ -88,13 +88,13 @@ mod tests { } let psp22_address = deploy_test_token(&mut session, 100)?; - let shielder_address = deploy_shielder(&mut session, &psp22_address)?; + let shielder_address = deploy_shielder(&mut session, vec![psp22_address.clone()])?; // CREATE ACCOUNT let mut user_shielded_data = create_shielder_account( &mut session, &shielder_address, - &psp22_address, + vec![psp22_address.clone()], rng.gen::().into(), )?; @@ -158,7 +158,7 @@ mod tests { } let psp22_address = deploy_test_token(&mut session, 800)?; - let shielder_address = deploy_shielder(&mut session, &psp22_address)?; + let shielder_address = deploy_shielder(&mut session, vec![psp22_address.clone()])?; for depositor_addr in &depositors { psp22_transfer(&mut session, &psp22_address, depositor_addr, 100)?; @@ -173,7 +173,7 @@ mod tests { user_shielded_data.push(create_shielder_account( &mut session, &shielder_address, - &psp22_address, + vec![psp22_address.clone()], rng.gen::().into(), )?); @@ -211,4 +211,85 @@ mod tests { Ok(()) } + + #[drink::test] + fn relayer_single_deposit_single_withdraw(mut session: Session) -> Result<()> { + let mut rng = StdRng::seed_from_u64(1); + + let alice = init_alice(&mut session)?; + let bob = init_bob(&mut session)?; + let relayer = init_relayer(&mut session)?; + + session = session.with_actor(alice.clone()); + + let psp22_address = deploy_test_token(&mut session, 100)?; + let azero_address = deploy_azero_test_token(&mut session, 100)?; + let shielder_address = deploy_shielder( + &mut session, + vec![psp22_address.clone(), azero_address.clone()], + )?; + + // CREATE ACCOUNT + let user_shielded_data = create_shielder_account( + &mut session, + &shielder_address, + vec![psp22_address.clone(), azero_address.clone()], + rng.gen::().into(), + )?; + + // APPROVE TRANSFER + psp22_approve(&mut session, &azero_address, &shielder_address, 10)?; + + // DEPOSIT + let user_shielded_data = shielder_update( + &mut session, + &shielder_address, + deposit_op(&azero_address, &alice, 10), + user_shielded_data, + rng.gen::().into(), + )?; + + // APPROVE TRANSFER + psp22_approve(&mut session, &psp22_address, &shielder_address, 10)?; + + session = session.with_actor(relayer.clone()); + // DEPOSIT THROUGH RELAYER + let user_shielded_data = shielder_update( + &mut session, + &shielder_address, + deposit_op_relayer(&psp22_address, &alice, 10, &azero_address, &relayer, 1), + user_shielded_data, + rng.gen::().into(), + )?; + + let alice_psp22_balance = get_psp22_balance(&mut session, &psp22_address, &alice)?; + assert_eq!(alice_psp22_balance, 90); + let shielder_psp22_balance = + get_psp22_balance(&mut session, &psp22_address, &shielder_address)?; + assert_eq!(shielder_psp22_balance, 10); + let relayer_psp22_balance = get_psp22_balance(&mut session, &azero_address, &relayer)?; + assert_eq!(relayer_psp22_balance, 1); + + // SWITCH TO bob + session = session.with_actor(bob.clone()); + + // WITHDRAW THROUGH RELAYER + let _ = shielder_update( + &mut session, + &shielder_address, + withdraw_op_relayer(&psp22_address, &bob, 1, &azero_address, &relayer, 1), + user_shielded_data, + rng.gen::().into(), + )?; + + let bob_psp22_balance = get_psp22_balance(&mut session, &psp22_address, &bob)?; + assert_eq!(bob_psp22_balance, 1); + let shielder_psp22_balance = + get_psp22_balance(&mut session, &psp22_address, &shielder_address)?; + assert_eq!(shielder_psp22_balance, 9); + let relayer_psp22_balance = get_psp22_balance(&mut session, &azero_address, &relayer)?; + assert_eq!(relayer_psp22_balance, 2); + + Ok(()) + } } diff --git a/shielder/drink_tests/src/utils/chain.rs b/shielder/drink_tests/src/utils/chain.rs index 5fe0f19..94ff576 100644 --- a/shielder/drink_tests/src/utils/chain.rs +++ b/shielder/drink_tests/src/utils/chain.rs @@ -26,3 +26,9 @@ pub fn init_bob(session: &mut Session) -> Result { init_acc_with_balance(session, &res)?; Ok(res) } + +pub fn init_relayer(session: &mut Session) -> Result { + let res = AccountId32::new([4u8; 32]); + init_acc_with_balance(session, &res)?; + Ok(res) +} diff --git a/shielder/drink_tests/src/utils/ops.rs b/shielder/drink_tests/src/utils/ops.rs index 7385d0b..63ece17 100644 --- a/shielder/drink_tests/src/utils/ops.rs +++ b/shielder/drink_tests/src/utils/ops.rs @@ -43,3 +43,49 @@ pub fn withdraw_op( }, } } + +pub fn deposit_op_relayer( + psp22_address: &AccountId32, + user: &AccountId32, + amount: u128, + azero_address: &AccountId32, + relayer: &AccountId32, + fee: u128, +) -> UpdateOperation { + UpdateOperation { + op_pub: OpPub::DepositRelayer { + amount, + token: Scalar::from_bytes(*((*psp22_address).as_ref())), + user: Scalar::from_bytes(*((*user).as_ref())), + fee, + fee_token: Scalar::from_bytes(*((*azero_address).as_ref())), + relayer: Scalar::from_bytes(*((*relayer).as_ref())), + }, + op_priv: OpPriv { + user: Scalar::from_bytes(*((*user).as_ref())), + }, + } +} + +pub fn withdraw_op_relayer( + psp22_address: &AccountId32, + user: &AccountId32, + amount: u128, + azero_address: &AccountId32, + relayer: &AccountId32, + fee: u128, +) -> UpdateOperation { + UpdateOperation { + op_pub: OpPub::WithdrawRelayer { + amount, + token: Scalar::from_bytes(*((*psp22_address).as_ref())), + user: Scalar::from_bytes(*((*user).as_ref())), + fee, + fee_token: Scalar::from_bytes(*((*azero_address).as_ref())), + relayer: Scalar::from_bytes(*((*relayer).as_ref())), + }, + op_priv: OpPriv { + user: Scalar::from_bytes(*((*user).as_ref())), + }, + } +} diff --git a/shielder/drink_tests/src/utils/psp22.rs b/shielder/drink_tests/src/utils/psp22.rs index 973337b..3d94833 100644 --- a/shielder/drink_tests/src/utils/psp22.rs +++ b/shielder/drink_tests/src/utils/psp22.rs @@ -1,7 +1,8 @@ +use crate::tests::BundleProvider; use anyhow::Result; use drink::{ minimal::MinimalSandbox, - session::{bundle::ContractBundle, Session, NO_ENDOWMENT, NO_SALT}, + session::{Session, NO_ENDOWMENT, NO_SALT}, AccountId32, }; @@ -9,8 +10,7 @@ pub fn deploy_test_token( session: &mut Session, supply: u128, ) -> Result { - let psp22_bundle = - ContractBundle::load(std::path::Path::new("../PSP22/target/ink/psp22.contract"))?; + let psp22_bundle = BundleProvider::Psp22.bundle()?; let res = session.deploy_bundle( psp22_bundle, "new", @@ -26,6 +26,26 @@ pub fn deploy_test_token( Ok(res) } +pub fn deploy_azero_test_token( + session: &mut Session, + supply: u128, +) -> Result { + let psp22_bundle = BundleProvider::Psp22.bundle()?; + let res = session.deploy_bundle( + psp22_bundle, + "new", + &[ + format!("{}", supply).as_str(), + "Some(\"AZERO\")", + "Some(\"AZERO\")", + "9", + ], + NO_SALT, + NO_ENDOWMENT, + )?; + Ok(res) +} + pub fn get_psp22_balance( session: &mut Session, token: &AccountId32, diff --git a/shielder/drink_tests/src/utils/shielder.rs b/shielder/drink_tests/src/utils/shielder.rs index 513b8f3..47fd152 100644 --- a/shielder/drink_tests/src/utils/shielder.rs +++ b/shielder/drink_tests/src/utils/shielder.rs @@ -25,11 +25,13 @@ pub struct ShielderUserEnv { pub fn deploy_shielder( session: &mut Session, - token: &AccountId32, + allowed_tokens: Vec, ) -> Result { let shielder_bundle = BundleProvider::ShielderContract.bundle()?; let mut tokens: [Scalar; TOKENS_NUMBER] = [0_u128.into(); TOKENS_NUMBER]; - tokens[0] = Scalar::from_bytes(*((*token).as_ref())); + for (i, token) in allowed_tokens.iter().enumerate() { + tokens[i] = Scalar::from_bytes(*((*token).as_ref())); + } let res = session.deploy_bundle( shielder_bundle, "new", @@ -43,11 +45,13 @@ pub fn deploy_shielder( pub fn create_shielder_account( session: &mut Session, shielder_address: &AccountId32, - token: &AccountId32, + allowed_tokens: Vec, nullifier: Scalar, ) -> Result { let mut tokens: [Scalar; TOKENS_NUMBER] = [0_u128.into(); TOKENS_NUMBER]; - tokens[0] = Scalar::from_bytes(*((*token).as_ref())); + for (i, token) in allowed_tokens.iter().enumerate() { + tokens[i] = Scalar::from_bytes(*((*token).as_ref())); + } let acc = Account::new(tokens); diff --git a/shielder/mocked_zk/src/account.rs b/shielder/mocked_zk/src/account.rs index 4c5b422..5fcb5a7 100644 --- a/shielder/mocked_zk/src/account.rs +++ b/shielder/mocked_zk/src/account.rs @@ -75,6 +75,86 @@ impl Account { } Err(ZkpError::AccountUpdateError) } + OpPub::DepositRelayer { + amount: op_amount, + token: op_token, + fee, + fee_token, + .. + } => { + let mut dep_token_idx = None; + let mut fee_token_idx = None; + for (i, (token, _balance)) in self.balances.into_iter().enumerate() { + if token == op_token { + dep_token_idx = Some(i); + } + if token == fee_token { + fee_token_idx = Some(i); + } + } + if dep_token_idx.is_none() || fee_token_idx.is_none() { + return Err(ZkpError::AccountUpdateError); + } + let dep_token_idx = dep_token_idx.unwrap(); + let fee_token_idx = fee_token_idx.unwrap(); + + let balance_upd: Scalar = ((u128::from(self.balances[dep_token_idx].1)) + .checked_add(op_amount) + .ok_or(ZkpError::AccountUpdateError)?) + .into(); + let fee_balance_upd: Scalar = ((u128::from(self.balances[fee_token_idx].1)) + .checked_sub(fee) + .ok_or(ZkpError::AccountUpdateError)?) + .into(); + + let mut balances_upd = self.balances; + balances_upd[dep_token_idx] = (op_token, balance_upd); + balances_upd[fee_token_idx] = (fee_token, fee_balance_upd); + + Ok(Self { + balances: balances_upd, + }) + } + OpPub::WithdrawRelayer { + amount: op_amount, + token: op_token, + fee, + fee_token, + .. + } => { + let mut dep_token_idx = None; + let mut fee_token_idx = None; + for (i, (token, _balance)) in self.balances.into_iter().enumerate() { + if token == op_token { + dep_token_idx = Some(i); + } + if token == fee_token { + fee_token_idx = Some(i); + } + } + if dep_token_idx.is_none() || fee_token_idx.is_none() { + return Err(ZkpError::AccountUpdateError); + } + let dep_token_idx = dep_token_idx.unwrap(); + let fee_token_idx = fee_token_idx.unwrap(); + + let balance_upd: Scalar = ((u128::from(self.balances[dep_token_idx].1)) + .checked_sub(op_amount) + .ok_or(ZkpError::AccountUpdateError)?) + .into(); + let fee_balance_upd: Scalar = ((u128::from(self.balances[fee_token_idx].1)) + .checked_sub(fee) + .ok_or(ZkpError::AccountUpdateError)?) + .into(); + + let mut balances_upd = self.balances; + balances_upd[dep_token_idx] = (op_token, balance_upd); + balances_upd[fee_token_idx] = (fee_token, fee_balance_upd); + + Ok(Self { + balances: balances_upd, + }) + } } } } diff --git a/shielder/mocked_zk/src/ops.rs b/shielder/mocked_zk/src/ops.rs index 12e6060..58c6c26 100644 --- a/shielder/mocked_zk/src/ops.rs +++ b/shielder/mocked_zk/src/ops.rs @@ -22,6 +22,36 @@ pub enum OpPub { /// User address, to who the tokens are transferred user: Scalar, }, + /// Deposit PSP-22 token through relayer + DepositRelayer { + /// amount of deposit + amount: u128, + /// PSP-22 token address + token: Scalar, + /// User address, from whom tokens are transferred + user: Scalar, + /// Fee amount for relayer + fee: u128, + /// PSP-22 token address + fee_token: Scalar, + /// Relayer address, from whom the transaction is initiated + relayer: Scalar, + }, + /// Withdraw PSP-22 token + WithdrawRelayer { + /// amount of withdrawal + amount: u128, + /// PSP-22 token address + token: Scalar, + /// User address, to who the tokens are transferred + user: Scalar, + /// Fee amount for relayer + fee: u128, + /// PSP-22 token address + fee_token: Scalar, + /// Relayer address, from whom the transaction is initiated + relayer: Scalar, + }, } /// empty private operation @@ -57,6 +87,16 @@ impl Operation { return Err(ZkpError::OperationCombineError); } } + OpPub::DepositRelayer { user, .. } => { + if user != op_priv.user { + return Err(ZkpError::OperationCombineError); + } + } + OpPub::WithdrawRelayer { user, .. } => { + if user != op_priv.user { + return Err(ZkpError::OperationCombineError); + } + } } Ok(Operation { op_pub, op_priv }) }