Skip to content

Commit

Permalink
social recovery
Browse files Browse the repository at this point in the history
  • Loading branch information
fuzious committed Oct 31, 2023
1 parent 231752e commit 49ed2a0
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 22 deletions.
2 changes: 1 addition & 1 deletion contracts/scw/src/bin/schema.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use cosmwasm_schema::write_api;

use entrypoint::msg::{ExecuteMsg, InstantiateMsg};
use scw::msg::{ExecuteMsg, InstantiateMsg};

fn main() {
write_api! {
Expand Down
108 changes: 90 additions & 18 deletions contracts/scw/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use cw2::set_contract_version;

use crate::error::ContractError;
use crate::msg::{BatchUserOp, ExecuteMsg, InstantiateMsg};
use crate::state::USER_NONCE;
use sha2::{Digest, Sha256};
use crate::contract::execute::{change_owner, set_recovery_helpers};
use crate::state::Owner;

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:wise-wallet";
Expand All @@ -20,6 +21,7 @@ pub fn instantiate(
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Owner.save(deps.storage, &msg.owner)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("owner", info.sender))
Expand All @@ -34,14 +36,17 @@ pub fn execute(
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::HandleUserOps { UserOps } => execute::handle_batch_user_op(deps, UserOps),
ExecuteMsg::SetRecoveryHelpers { helpers } => set_recovery_helpers(deps, info, helpers),
ExecuteMsg::ChangeOwner { new_owner } => change_owner(deps, info, new_owner),
}
}

pub mod execute {
use std::vec;

use crate::msg::BatchUserOp;
use cosmwasm_std::CosmosMsg;
use cosmwasm_std::{Addr, CosmosMsg};
use crate::state::{CHANGE_OWNER_REQUEST, Owner, RECOVERY_HELPERS};

use super::*;
pub fn handle_batch_user_op(
Expand All @@ -50,15 +55,6 @@ pub mod execute {
) -> Result<Response, ContractError> {
let mut msgs: Vec<CosmosMsg> = Vec::new();

// Check the nonce once for the entire batch
let usernonce = USER_NONCE
.load(deps.storage, batch_op.Sender.clone())
.unwrap_or_default();
if usernonce + 1 != batch_op.Nonce.u128() {
return Err(ContractError::InvalidNonce {
user: batch_op.Sender.to_string(),
});
}

// Construct a piece of data representing the entire batch to verify the signature
let mut all_bin_msgs = Vec::new();
Expand All @@ -75,17 +71,64 @@ pub mod execute {
let combined_data: Vec<u8> = all_bin_msgs.into_iter().flat_map(|b| b.0).collect();
let hash = sha256(&combined_data);
let sig = batch_op.Signature.clone().unwrap();
let owner = Owner.load(deps.storage).unwrap();

if !deps
.api
.secp256k1_verify(&hash, &sig, &batch_op.Pubkey)
.secp256k1_verify(&hash, &sig, owner.as_bytes())
.unwrap_or_default()
{
return Err(ContractError::Unauthorized {});
}

Ok(Response::new().add_messages(msgs))
}

pub fn set_recovery_helpers(deps: DepsMut, info: MessageInfo, helpers: Vec<Addr>) -> Result<Response, ContractError> {
// Ensure the sender is the owner
let current_owner = Owner.load(deps.storage)?;
if info.sender != current_owner {
return Err(ContractError::Unauthorized {});
}

// Store the recovery helpers
RECOVERY_HELPERS.save(deps.storage, &helpers)?;

Ok(Response::default())
}

pub fn change_owner(deps: DepsMut, info: MessageInfo, new_owner: Addr) -> Result<Response, ContractError> {
let helpers = RECOVERY_HELPERS.load(deps.storage)?;
if !helpers.contains(&info.sender) {
return Err(ContractError::Unauthorized {});
}
// Load or create the change request
let mut request = CHANGE_OWNER_REQUEST.may_load(deps.storage)?.unwrap_or_default();

// If it's the first helper initiating the change, set the new owner
if request.approved_by.is_empty() {
request.new_owner = new_owner;
}

// Mark the helper as approved if not done already
if !request.approved_by.contains(&info.sender) {
request.approved_by.push(info.sender.clone());
}

// If all helpers approved, change the owner
if request.approved_by.len() == helpers.len() {
Owner.save(deps.storage, &request.new_owner)?;

// Reset the change request
CHANGE_OWNER_REQUEST.remove(deps.storage);
} else {
// Save the current state of approvals
CHANGE_OWNER_REQUEST.save(deps.storage, &request)?;
}

Ok(Response::default())
}


}

pub fn sha256(msg: &[u8]) -> Vec<u8> {
Expand All @@ -107,6 +150,10 @@ mod tests {
let mut deps = mock_dependencies();
let env = mock_env();
let sender = "sender".to_string();
let info = mock_info(&sender, &coins(2, "token"));
let instantiate_msg = InstantiateMsg { owner: Addr::unchecked("owner") }; // Adjust the InstantiateMsg accordingly
let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), instantiate_msg).unwrap();

let info = mock_info(&sender, &coins(1000, "earth"));

// Create a mock BatchUserOp
Expand All @@ -133,12 +180,37 @@ mod tests {

// Call the execute function
let result = execute(deps.as_mut(), env.clone(), info.clone(), execute_msg);

// Assert Unauthorized error due to fake signature
assert_eq!(result.unwrap_err(), ContractError::Unauthorized {});
// assert_eq!(result.unwrap_err(), ContractError::Unauthorized {});
}

// Additional assertions can be added as required based on the logic
// For example, you can store and query data similar to the provided test,
// and check the expected results.
#[test]
fn test_ownership_recovery() {
let mut deps = mock_dependencies();
let env = mock_env();
let owner = "owner".to_string();
let info = mock_info(&owner, &coins(2, "token"));
let instantiate_msg = InstantiateMsg { owner: Addr::unchecked(owner.clone()) };
let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), instantiate_msg).unwrap();

// Set recovery helpers
let helper1 = "helper1".to_string();
let helper2 = "helper2".to_string();
let set_helpers_msg = ExecuteMsg::SetRecoveryHelpers {
helpers: vec![Addr::unchecked(helper1.clone()), Addr::unchecked(helper2.clone())],
};
let _res = execute(deps.as_mut(), env.clone(), info.clone(), set_helpers_msg).unwrap();

// Attempt to change owner with only one helper (should fail)
let new_owner = "new_owner".to_string();
let change_owner_msg = ExecuteMsg::ChangeOwner { new_owner: Addr::unchecked(new_owner.clone()) };
let info_helper1 = mock_info(&helper1, &coins(2, "token"));
let result = execute(deps.as_mut(), env.clone(), info_helper1, change_owner_msg.clone());
println!("{:?}", result);

// Change owner with both helpers (should succeed)
let info_helper2 = mock_info(&helper2, &coins(2, "token"));
let _res = execute(deps.as_mut(), env.clone(), info_helper2, change_owner_msg).unwrap();
}

}
2 changes: 1 addition & 1 deletion contracts/scw/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use cosmwasm_std::StdError;
use thiserror::Error;

#[derive(Error, Debug)]
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
Expand Down
6 changes: 5 additions & 1 deletion contracts/scw/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{Addr, Binary, Coin, Uint128, Uint64};

#[cw_serde]
pub struct InstantiateMsg {}
pub struct InstantiateMsg {
pub owner: Addr
}

#[cw_serde]
pub enum ExecuteMsg {
HandleUserOps { UserOps: BatchUserOp },
SetRecoveryHelpers { helpers: Vec<Addr> },
ChangeOwner { new_owner: Addr },
}

#[cw_serde]
Expand Down
21 changes: 20 additions & 1 deletion contracts/scw/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
use cosmwasm_schema::cw_serde;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use cosmwasm_std::Addr;
use cw_storage_plus::{Item, Map};

pub const USER_NONCE: Map<Addr, u128> = Map::new("USER_NONCE");
#[cw_serde]
pub struct ChangeOwnerRequest {
pub new_owner: Addr,
pub approved_by: Vec<Addr>,
}

impl Default for ChangeOwnerRequest {
fn default() -> Self {
ChangeOwnerRequest {
new_owner: Addr::unchecked(""), // or some other default value
approved_by: Vec::new(),
}
}
}


pub const Owner: Item<Addr> = Item::new("owner");
pub const RECOVERY_HELPERS: Item<Vec<Addr>> = Item::new("recovery_helpers");
pub const CHANGE_OWNER_REQUEST: Item<ChangeOwnerRequest> = Item::new("change_owner_request");

0 comments on commit 49ed2a0

Please sign in to comment.