Skip to content

Commit

Permalink
[FIX-8] Admin transfer procedure can be enhanced
Browse files Browse the repository at this point in the history
  • Loading branch information
iboss-ptk committed Sep 21, 2023
1 parent 73e6d4e commit 9a80ced
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 4 deletions.
130 changes: 127 additions & 3 deletions contracts/transmuter/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,57 @@ impl<'a> Admin<'a> {
.save(deps.storage, &AdminState::Claimed(sender))
.map_err(Into::into)
}

/// Cancel admin transfer
pub fn cancel_transfer(&self, deps: DepsMut, sender: Addr) -> Result<(), ContractError> {
match self.state(deps.as_ref())? {
AdminState::Claimed(_) => Err(ContractError::InoperableAdminTransferringState {}),
AdminState::Transferring { current, .. } => {
// Make sure that the sender is the current admin
ensure!(sender == current, ContractError::Unauthorized {});

// Cancel the transfer
self.state
.save(deps.storage, &AdminState::Claimed(current))
.map_err(Into::into)
}
}
}

/// Reject admin transfer
pub fn reject_transfer(&self, deps: DepsMut, sender: Addr) -> Result<(), ContractError> {
match self.state(deps.as_ref())? {
AdminState::Claimed(_) => Err(ContractError::InoperableAdminTransferringState {}),
AdminState::Transferring { current, candidate } => {
// Make sure that the sender is the candidate
ensure!(candidate == sender, ContractError::Unauthorized {});

// Cancel the transfer
self.state
.save(deps.storage, &AdminState::Claimed(current))
.map_err(Into::into)
}
}
}

/// Renounce admin rights
pub fn renounce(&self, deps: DepsMut, sender: Addr) -> Result<(), ContractError> {
// Make sure that the sender is the current admin
let current_admin = self.current(deps.as_ref())?;
ensure!(sender == current_admin, ContractError::Unauthorized {});

// Set the current admin to the candidate
self.state.remove(deps.storage);

Ok(())
}

fn state(&self, deps: Deps) -> Result<AdminState, ContractError> {
self.state
.may_load(deps.storage)?
.ok_or(StdError::not_found("admin"))
.map_err(Into::into)
}
}

/// Ensure that the sender is the current admin
Expand Down Expand Up @@ -160,20 +211,93 @@ mod tests {

// Claim admin rights with unauthorized sender
assert_eq!(
admin.claim(deps.as_mut(), admin_addr),
admin.claim(deps.as_mut(), admin_addr.clone()),
Err(ContractError::Unauthorized {})
);

assert_eq!(
admin.claim(deps.as_mut(), random_addr),
admin.claim(deps.as_mut(), random_addr.clone()),
Err(ContractError::Unauthorized {})
);

// Claim admin rights
assert_eq!(admin.claim(deps.as_mut(), candidate_addr.clone()), Ok(()));

// New state
assert_eq!(admin.current(deps.as_ref()), Ok(candidate_addr));
assert_eq!(admin.current(deps.as_ref()), Ok(candidate_addr.clone()));
assert_eq!(admin.candidate(deps.as_ref()), Ok(None));

let new_admin_addr = candidate_addr;
let old_admin_addr = admin_addr;

// Transfer admin rights by new admin
assert_eq!(
admin.transfer(deps.as_mut(), new_admin_addr.clone(), random_addr.clone()),
Ok(())
);

// Cancel transfer by non-admin
assert_eq!(
admin
.cancel_transfer(deps.as_mut(), old_admin_addr.clone())
.unwrap_err(),
ContractError::Unauthorized {}
);

// Cancel transfer by admin
assert_eq!(
admin.cancel_transfer(deps.as_mut(), new_admin_addr.clone()),
Ok(())
);

assert_eq!(
admin.state.load(&deps.storage).unwrap(),
AdminState::Claimed(new_admin_addr.clone())
);

// Cancel transfer by admin when not transferring
assert_eq!(
admin.cancel_transfer(deps.as_mut(), new_admin_addr.clone()),
Err(ContractError::InoperableAdminTransferringState {})
);

// Transfer admin rights by new admin again
assert_eq!(
admin.transfer(deps.as_mut(), new_admin_addr.clone(), random_addr.clone()),
Ok(())
);

// reject by non-candidate
assert_eq!(
admin
.reject_transfer(deps.as_mut(), old_admin_addr.clone())
.unwrap_err(),
ContractError::Unauthorized {}
);

// reject by candidate
assert_eq!(admin.reject_transfer(deps.as_mut(), random_addr), Ok(()));

// reject by admin when not transferring
assert_eq!(
admin.reject_transfer(deps.as_mut(), new_admin_addr.clone()),
Err(ContractError::InoperableAdminTransferringState {})
);

assert_eq!(
admin.state.load(&deps.storage).unwrap(),
AdminState::Claimed(new_admin_addr.clone())
);

// renounce by non-admin
assert_eq!(
admin.renounce(deps.as_mut(), old_admin_addr).unwrap_err(),
ContractError::Unauthorized {}
);

// renounce by admin
assert_eq!(admin.renounce(deps.as_mut(), new_admin_addr), Ok(()));

assert_eq!(admin.state.may_load(&deps.storage).unwrap(), None);
}
}
139 changes: 138 additions & 1 deletion contracts/transmuter/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,28 @@ impl Transmuter<'_> {
.add_attribute("candidate", candidate))
}

#[msg(exec)]
pub fn cancel_admin_transfer(
&self,
ctx: (DepsMut, Env, MessageInfo),
) -> Result<Response, ContractError> {
let (deps, _env, info) = ctx;
self.admin.cancel_transfer(deps, info.sender)?;

Ok(Response::new().add_attribute("method", "cancel_admin_transfer"))
}

#[msg(exec)]
pub fn reject_admin_transfer(
&self,
ctx: (DepsMut, Env, MessageInfo),
) -> Result<Response, ContractError> {
let (deps, _env, info) = ctx;
self.admin.reject_transfer(deps, info.sender)?;

Ok(Response::new().add_attribute("method", "reject_admin_transfer"))
}

#[msg(exec)]
pub fn claim_admin(&self, ctx: (DepsMut, Env, MessageInfo)) -> Result<Response, ContractError> {
let (deps, _env, info) = ctx;
Expand All @@ -797,6 +819,17 @@ impl Transmuter<'_> {
.add_attribute("new_admin", sender_string))
}

#[msg(exec)]
pub fn renounce_adminship(
&self,
ctx: (DepsMut, Env, MessageInfo),
) -> Result<Response, ContractError> {
let (deps, _env, info) = ctx;
self.admin.renounce(deps, info.sender)?;

Ok(Response::new().add_attribute("method", "renounce_adminship"))
}

#[msg(query)]
fn get_admin(&self, ctx: (Deps, Env)) -> Result<GetAdminResponse, ContractError> {
let (deps, _env) = ctx;
Expand Down Expand Up @@ -1095,6 +1128,8 @@ mod tests {
.update_balance("someone", vec![Coin::new(1, "uosmo"), Coin::new(1, "uion")]);

let admin = "admin";
let canceling_candidate = "canceling_candidate";
let rejecting_candidate = "rejecting_candidate";
let candidate = "candidate";
let init_msg = InstantiateMsg {
pool_asset_denoms: vec!["uosmo".to_string(), "uion".to_string()],
Expand All @@ -1107,6 +1142,88 @@ mod tests {
// Instantiate the contract.
instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap();

// Transfer admin rights to the canceling candidate
let transfer_admin_msg = ContractExecMsg::Transmuter(ExecMsg::TransferAdmin {
candidate: canceling_candidate.to_string(),
});
execute(deps.as_mut(), env.clone(), info.clone(), transfer_admin_msg).unwrap();

// Check the admin candidate
let res = query(
deps.as_ref(),
env.clone(),
ContractQueryMsg::Transmuter(QueryMsg::GetAdminCandidate {}),
)
.unwrap();
let admin_candidate: GetAdminCandidateResponse = from_binary(&res).unwrap();
assert_eq!(
admin_candidate.admin_candidate.unwrap().as_str(),
canceling_candidate
);

// Cancel admin rights transfer
let cancel_admin_transfer_msg =
ContractExecMsg::Transmuter(ExecMsg::CancelAdminTransfer {});

execute(
deps.as_mut(),
env.clone(),
mock_info(admin, &[]),
cancel_admin_transfer_msg,
)
.unwrap();

// Check the admin candidate
let res = query(
deps.as_ref(),
env.clone(),
ContractQueryMsg::Transmuter(QueryMsg::GetAdminCandidate {}),
)
.unwrap();
let admin_candidate: GetAdminCandidateResponse = from_binary(&res).unwrap();
assert_eq!(admin_candidate.admin_candidate, None);

// Transfer admin rights to the rejecting candidate
let transfer_admin_msg = ContractExecMsg::Transmuter(ExecMsg::TransferAdmin {
candidate: rejecting_candidate.to_string(),
});
execute(deps.as_mut(), env.clone(), info.clone(), transfer_admin_msg).unwrap();

// Check the admin candidate
let res = query(
deps.as_ref(),
env.clone(),
ContractQueryMsg::Transmuter(QueryMsg::GetAdminCandidate {}),
)
.unwrap();
let admin_candidate: GetAdminCandidateResponse = from_binary(&res).unwrap();
assert_eq!(
admin_candidate.admin_candidate.unwrap().as_str(),
rejecting_candidate
);

// Reject admin rights transfer
let reject_admin_transfer_msg =
ContractExecMsg::Transmuter(ExecMsg::RejectAdminTransfer {});

execute(
deps.as_mut(),
env.clone(),
mock_info(rejecting_candidate, &[]),
reject_admin_transfer_msg,
)
.unwrap();

// Check the admin candidate
let res = query(
deps.as_ref(),
env.clone(),
ContractQueryMsg::Transmuter(QueryMsg::GetAdminCandidate {}),
)
.unwrap();
let admin_candidate: GetAdminCandidateResponse = from_binary(&res).unwrap();
assert_eq!(admin_candidate.admin_candidate, None);

// Transfer admin rights to the candidate
let transfer_admin_msg = ContractExecMsg::Transmuter(ExecMsg::TransferAdmin {
candidate: candidate.to_string(),
Expand Down Expand Up @@ -1136,12 +1253,32 @@ mod tests {
// Check the current admin
let res = query(
deps.as_ref(),
env,
env.clone(),
ContractQueryMsg::Transmuter(QueryMsg::GetAdmin {}),
)
.unwrap();
let admin: GetAdminResponse = from_binary(&res).unwrap();
assert_eq!(admin.admin.as_str(), candidate);

// Renounce admin rights
let renounce_admin_msg = ContractExecMsg::Transmuter(ExecMsg::RenounceAdminship {});
execute(
deps.as_mut(),
env.clone(),
mock_info(candidate, &[]),
renounce_admin_msg,
)
.unwrap();

// Check the current admin
let err = query(
deps.as_ref(),
env.clone(),
ContractQueryMsg::Transmuter(QueryMsg::GetAdmin {}),
)
.unwrap_err();

assert_eq!(err, ContractError::Std(StdError::not_found("admin")));
}

#[test]
Expand Down
3 changes: 3 additions & 0 deletions contracts/transmuter/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ pub enum ContractError {
#[error("Unauthorized")]
Unauthorized {},

#[error("Admin transferring state is inoperable for the requested operation")]
InoperableAdminTransferringState {},

#[error("Limiter count for {denom} exceed maximum per denom: {max}")]
MaxLimiterCountPerDenomExceeded { denom: String, max: Uint64 },

Expand Down

0 comments on commit 9a80ced

Please sign in to comment.