From be477f395dc0566559f00bb81898649d5a852733 Mon Sep 17 00:00:00 2001 From: Janita Chalam Date: Mon, 30 May 2022 15:49:25 -0700 Subject: [PATCH 1/2] add execute msg to update minter --- contracts/cw20-base/src/contract.rs | 73 +++++++++++++++++++++++++++++ packages/cw20/src/msg.rs | 2 + 2 files changed, 75 insertions(+) diff --git a/contracts/cw20-base/src/contract.rs b/contracts/cw20-base/src/contract.rs index 72f7b3d53..145812ea8 100644 --- a/contracts/cw20-base/src/contract.rs +++ b/contracts/cw20-base/src/contract.rs @@ -225,6 +225,9 @@ pub fn execute( marketing, } => execute_update_marketing(deps, env, info, project, description, marketing), ExecuteMsg::UploadLogo(logo) => execute_upload_logo(deps, env, info, logo), + ExecuteMsg::UpdateMinter { new_minter } => { + execute_update_minter(deps, env, info, new_minter) + } } } @@ -377,6 +380,32 @@ pub fn execute_send( Ok(res) } +pub fn execute_update_minter( + deps: DepsMut, + _env: Env, + info: MessageInfo, + new_minter: String, +) -> Result { + let mut config = TOKEN_INFO.load(deps.storage)?; + // Check that sender is authorized to update minter + if config.mint.is_none() || config.mint.as_ref().unwrap().minter != info.sender { + return Err(ContractError::Unauthorized {}); + } + + let minter = deps.api.addr_validate(&new_minter)?; + let minter_data = MinterData { + minter, + cap: config.mint.unwrap().cap, + }; + config.mint = Some(minter_data); + + TOKEN_INFO.save(deps.storage, &config)?; + + Ok(Response::default() + .add_attribute("action", "update_minter") + .add_attribute("minter", new_minter)) +} + pub fn execute_update_marketing( deps: DepsMut, _env: Env, @@ -871,6 +900,50 @@ mod tests { assert_eq!(err, ContractError::Unauthorized {}); } + #[test] + fn minter_can_update_itself() { + let mut deps = mock_dependencies(); + let minter = String::from("minter"); + do_instantiate_with_minter( + deps.as_mut(), + &String::from("genesis"), + Uint128::new(1234), + &minter, + None, + ); + + let msg = ExecuteMsg::UpdateMinter { + new_minter: String::from("new_minter"), + }; + + let info = mock_info(&minter, &[]); + let env = mock_env(); + let res = execute(deps.as_mut(), env, info, msg); + assert!(res.is_ok()); + } + + #[test] + fn others_cannot_update_minter() { + let mut deps = mock_dependencies(); + let minter = String::from("minter"); + do_instantiate_with_minter( + deps.as_mut(), + &String::from("genesis"), + Uint128::new(1234), + &minter, + None, + ); + + let msg = ExecuteMsg::UpdateMinter { + new_minter: String::from("new_minter"), + }; + + let info = mock_info("not the minter", &[]); + let env = mock_env(); + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!(err, ContractError::Unauthorized {}); + } + #[test] fn no_one_mints_if_minter_unset() { let mut deps = mock_dependencies(); diff --git a/packages/cw20/src/msg.rs b/packages/cw20/src/msg.rs index 4a7518b54..4cd6fba63 100644 --- a/packages/cw20/src/msg.rs +++ b/packages/cw20/src/msg.rs @@ -55,6 +55,8 @@ pub enum Cw20ExecuteMsg { /// Only with the "mintable" extension. If authorized, creates amount new tokens /// and adds to the recipient balance. Mint { recipient: String, amount: Uint128 }, + /// Only with the "mintable" extension. The current minter may set a new minter. + UpdateMinter { new_minter: String }, /// Only with the "marketing" extension. If authorized, updates marketing metadata. /// Setting None/null for any of these will leave it unchanged. /// Setting Some("") will clear this field on the contract storage From 0a6546bc3fb3b337d34a47bf4763c677520ea44a Mon Sep 17 00:00:00 2001 From: Janita Chalam Date: Wed, 8 Jun 2022 09:48:26 -0700 Subject: [PATCH 2/2] resolve comments --- contracts/cw20-base/src/contract.rs | 43 +++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/contracts/cw20-base/src/contract.rs b/contracts/cw20-base/src/contract.rs index 145812ea8..2bdf482cb 100644 --- a/contracts/cw20-base/src/contract.rs +++ b/contracts/cw20-base/src/contract.rs @@ -307,8 +307,17 @@ pub fn execute_mint( return Err(ContractError::InvalidZeroAmount {}); } - let mut config = TOKEN_INFO.load(deps.storage)?; - if config.mint.is_none() || config.mint.as_ref().unwrap().minter != info.sender { + let mut config = TOKEN_INFO + .may_load(deps.storage)? + .ok_or(ContractError::Unauthorized {})?; + + if config + .mint + .as_ref() + .ok_or(ContractError::Unauthorized {})? + .minter + != info.sender + { return Err(ContractError::Unauthorized {}); } @@ -386,16 +395,19 @@ pub fn execute_update_minter( info: MessageInfo, new_minter: String, ) -> Result { - let mut config = TOKEN_INFO.load(deps.storage)?; - // Check that sender is authorized to update minter - if config.mint.is_none() || config.mint.as_ref().unwrap().minter != info.sender { + let mut config = TOKEN_INFO + .may_load(deps.storage)? + .ok_or(ContractError::Unauthorized {})?; + + let mint = config.mint.as_ref().ok_or(ContractError::Unauthorized {})?; + if mint.minter != info.sender { return Err(ContractError::Unauthorized {}); } let minter = deps.api.addr_validate(&new_minter)?; let minter_data = MinterData { minter, - cap: config.mint.unwrap().cap, + cap: mint.cap, }; config.mint = Some(minter_data); @@ -403,7 +415,7 @@ pub fn execute_update_minter( Ok(Response::default() .add_attribute("action", "update_minter") - .add_attribute("minter", new_minter)) + .add_attribute("new_minter", new_minter)) } pub fn execute_update_marketing( @@ -901,25 +913,34 @@ mod tests { } #[test] - fn minter_can_update_itself() { + fn minter_can_update_minter_but_not_cap() { let mut deps = mock_dependencies(); let minter = String::from("minter"); + let cap = Some(Uint128::from(3000000u128)); do_instantiate_with_minter( deps.as_mut(), &String::from("genesis"), Uint128::new(1234), &minter, - None, + cap, ); + let new_minter = "new_minter"; let msg = ExecuteMsg::UpdateMinter { - new_minter: String::from("new_minter"), + new_minter: new_minter.to_string(), }; let info = mock_info(&minter, &[]); let env = mock_env(); - let res = execute(deps.as_mut(), env, info, msg); + let res = execute(deps.as_mut(), env.clone(), info, msg); assert!(res.is_ok()); + let query_minter_msg = QueryMsg::Minter {}; + let res = query(deps.as_ref(), env, query_minter_msg); + let mint: MinterResponse = from_binary(&res.unwrap()).unwrap(); + + // Minter cannot update cap. + assert!(mint.cap == cap); + assert!(mint.minter == new_minter) } #[test]