-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add option to deactivate a campaign #114
Conversation
WalkthroughThe update introduces significant enhancements to an airdrop contract, integrating a new Changes
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (invoked as PR comments)
Additionally, you can add CodeRabbit Configration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 4
Configuration used: CodeRabbit UI
Files ignored due to filter (1)
- contracts/airdrop/Cargo.toml
Files selected for processing (7)
- contracts/airdrop/src/contract.rs (7 hunks)
- contracts/airdrop/src/msg.rs (2 hunks)
- contracts/airdrop/src/state.rs (1 hunks)
- contracts/airdrop/src/tests/execute/reward_users.rs (8 hunks)
- contracts/airdrop/src/tests/execute/withdraw.rs (5 hunks)
- contracts/airdrop/src/tests/instantiate.rs (4 hunks)
- contracts/airdrop/src/tests/query/campaign.rs (2 hunks)
Files skipped from review due to trivial changes (1)
- contracts/airdrop/src/msg.rs
Additional comments: 17
contracts/airdrop/src/state.rs (1)
- 15-15: The addition of the
is_active
field to theCampaign
struct is a significant change. Ensure that all functionalities that interact with theCampaign
struct are updated to handle this new field correctly.contracts/airdrop/src/tests/query/campaign.rs (1)
- 21-24: The addition of the
is_active
field in theCampaign
struct instantiation within the test is correct. This ensures that the tests will now take into account the newis_active
field.contracts/airdrop/src/tests/instantiate.rs (3)
34-34: The addition of the
is_active
field in theCampaign
struct instantiation within the test is correct. This ensures that the tests will now take into account the newis_active
field.51-52: The error handling for the
instantiate
function with no funds is correct. It checks for the presence of only one coin, which aligns with the contract's requirements.70-73: The error handling for the
instantiate
function with an invalid denomination is correct. It ensures that only native tokens are allowed, which aligns with the contract's requirements.contracts/airdrop/src/tests/execute/withdraw.rs (2)
53-53: The check for the contract's unallocated amount being zero after withdrawal in the
test_withdraw_ok
function is correct. This ensures that the contract state is consistent with the expected behavior after a withdrawal.98-98: In the
test_withdraw_less_than_total_amount
function, the assertion that the unallocated amount is correct after a partial withdrawal is good. It validates that the contract state is updated correctly.contracts/airdrop/src/tests/execute/reward_users.rs (4)
80-80: The
test_reward_users_fully_allocated
function correctly checks that the campaign's unallocated amount is zero after rewarding users, which is consistent with the expected behavior.169-169: The
test_reward_users_as_manager
function correctly includes theis_active
field in theCampaign
struct. This ensures that the tests will now take into account the newis_active
field.231-231: The
test_fails_when_we_try_to_allocate_more_than_available
function correctly handles the error when trying to allocate more rewards than available in the campaign. This ensures that the contract does not allow over-allocation.282-282: The
test_fails_we_allocate_inactive
function correctly tests the scenario where a reward allocation should fail if the campaign is inactive. This validates the newis_active
logic.contracts/airdrop/src/contract.rs (6)
45-45: The
instantiate
function correctly initializes theis_active
field totrue
when a new campaign is created. This aligns with the expected default state of a new campaign.95-95: The addition of the
DesactivateCampaign
variant to theExecuteMsg
enum is correct. This allows for the deactivation of campaigns through a new message type.116-118: The
reward_users
function correctly checks if the campaign is active before proceeding with rewarding users. This ensures that inactive campaigns do not distribute rewards.168-170: The
claim
function correctly checks if the campaign is active before allowing users to claim rewards. This ensures that claims are only processed for active campaigns.256-272: The
withdraw
function correctly updates the campaign's unallocated amount after a withdrawal. This ensures that the contract state remains consistent with the actual funds available.302-304: The
query_user_reward
function correctly checks if the campaign is active before providing the user reward information. This ensures that the query reflects the current state of the campaign.
unallocated_amount: Uint128::new(1000), | ||
is_active: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure that the test cases cover scenarios where the is_active
field is both true
and false
. This will validate the behavior of the system when a campaign is active and when it is not.
// if i desactivate the campaign, everything should be withdrawn | ||
let resp = desactivate(deps.as_mut(), env.clone(), mock_info("owner", &[])) | ||
.unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic to deactivate the campaign in the test_withdraw_less_than_total_amount
function is correct. However, ensure that there are additional test cases to cover scenarios where the deactivation should fail, such as when a non-owner attempts to deactivate the campaign.
Codecov ReportAttention:
Additional details and impacted files
|
contracts/airdrop/src/contract.rs
Outdated
@@ -92,6 +92,7 @@ pub fn execute( | |||
} | |||
ExecuteMsg::Claim {} => claim(deps, env, info), | |||
ExecuteMsg::Withdraw { amount } => withdraw(deps, env, info, amount), | |||
ExecuteMsg::DesactivateCampaign {} => desactivate(deps, env, info), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DeactivateCampaign, same with the func.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 1
Configuration used: CodeRabbit UI
Files ignored due to filter (1)
- Cargo.lock
Files selected for processing (4)
- contracts/airdrop/src/contract.rs (7 hunks)
- contracts/airdrop/src/msg.rs (2 hunks)
- contracts/airdrop/src/tests/execute/reward_users.rs (8 hunks)
- contracts/airdrop/src/tests/execute/withdraw.rs (5 hunks)
Files skipped from review as they are similar to previous changes (3)
- contracts/airdrop/src/msg.rs
- contracts/airdrop/src/tests/execute/reward_users.rs
- contracts/airdrop/src/tests/execute/withdraw.rs
Additional comments: 6
contracts/airdrop/src/contract.rs (6)
45-45: The addition of the
is_active
field to theCampaign
struct is consistent with the PR's objective to allow campaigns to be toggled active or inactive.95-95: The addition of the
DeactivateCampaign
variant to theExecuteMsg
enum aligns with the new feature to deactivate campaigns.107-118: The
reward_users
function now includes a check to ensure that the campaign is active before proceeding. This is a necessary change to align with the new feature.168-170: The
claim
function has been correctly updated to check the campaign's active status before allowing a claim to be processed.255-272: The
withdraw
function has been updated to handle the campaign's unallocated amount correctly. It ensures that the unallocated amount is not negative after a withdrawal, which is a crucial check for maintaining the integrity of the campaign's funds.302-304: The
query_user_reward
function now includes a check for the campaign's active status, which is consistent with the other changes and necessary for the new feature.
contracts/airdrop/src/contract.rs
Outdated
if !campaign.is_active { | ||
return Err(StdError::generic_err("Campaign is not active")); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous comment from the bot suggests that the deactivate
function should not return an error if the campaign is already inactive. This change has not been addressed in the provided code. The function should be idempotent, meaning calling it on an already inactive campaign should not be an error but rather a no-op or return a success response.
- if !campaign.is_active {
- return Err(StdError::generic_err("Campaign is not active"));
- }
Committable suggestion
❗ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
if !campaign.is_active { | |
return Err(StdError::generic_err("Campaign is not active")); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of like the idempotency suggestion here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@k-yang, I'm glad to hear you're considering the idempotency suggestion. Here's how you can adjust the deactivate
function to be idempotent:
pub fn deactivate(
deps: DepsMut,
env: Env,
info: MessageInfo,
) -> Result<Response, StdError> {
let mut campaign = CAMPAIGN
.load(deps.storage)
.map_err(|_| StdError::generic_err("Failed to load campaign data"))?;
if campaign.owner != info.sender && !campaign.managers.contains(&info.sender) {
return Err(StdError::generic_err("Unauthorized"));
}
// If the campaign is already inactive, we simply return a success response.
if !campaign.is_active {
return Ok(Response::new().add_attribute("method", "deactivate"));
}
campaign.is_active = false;
CAMPAIGN.save(deps.storage, &campaign)?;
let bond_denom = deps.querier.query_bonded_denom()?;
let own_balance: Uint128 = deps
.querier
.query_balance(&env.contract.address, bond_denom.clone())
.map_err(|_| StdError::generic_err("Failed to query contract balance"))?
.amount;
return withdraw(deps, env, info, own_balance);
}
This modification ensures that if the deactivate
function is called on an already inactive campaign, it will not perform any state changes and will return a success response instead of an error.
contracts/airdrop/src/contract.rs
Outdated
if !campaign.is_active { | ||
return Err(StdError::generic_err("Campaign is not active")); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of like the idempotency suggestion here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 0
Configuration used: CodeRabbit UI
Files selected for processing (1)
- contracts/airdrop/src/contract.rs (7 hunks)
Additional comments: 7
contracts/airdrop/src/contract.rs (7)
45-45: The addition of the
is_active
field to theCampaign
struct is consistent with the PR's objective to allow campaigns to be toggled active or inactive.95-95: The
DeactivateCampaign
variant has been correctly added to theExecuteMsg
enum to handle campaign deactivation messages.107-118: The
reward_users
function now includes a check to ensure that the campaign is active before proceeding. This is a crucial check for the new feature's correctness.168-170: The
claim
function has been updated to include a check for the campaign's active status, which is necessary to prevent claims from inactive campaigns.190-221: The
deactivate
function has been implemented to handle the deactivation of a campaign. It includes authorization checks and sets theis_active
flag tofalse
. The function ends with a call towithdraw
, which may not be the expected behavior for deactivation. Typically, deactivation would not trigger a withdrawal of all funds. This should be verified for correctness.Verification successful
The verification process has revealed that the
deactivate
function's behavior to withdraw all funds upon deactivation is intentional, as indicated by the comments in the test files. This aligns with the implementation in thecontract.rs
file, confirming that the withdrawal of all funds is the expected behavior when a campaign is deactivated.* 257-274: The `withdraw` function has been updated to modify the `unallocated_amount` in the `Campaign` struct. It correctly handles the case where the withdrawal amount is greater than the unallocated amount by setting it to zero. This change is consistent with the logic of maintaining the integrity of the campaign's funds.Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Verify if the deactivation should indeed trigger a withdrawal. # Search for discussions or documentation related to the deactivation behavior. rg "DeactivateCampaign" -C 10Length of output: 2388
Script:
#!/bin/bash # Search for documentation or comments that explain the deactivation behavior, especially regarding the withdrawal of funds. rg "deactivate" -C 10 # Additionally, look for commit messages that might provide context on why the withdrawal is part of the deactivation process. git log -S "deactivate" --grep="deactivate" --oneline -pLength of output: 13505
- 303-307: The
query_user_reward
function now checks if the campaign is active before returning the user's reward. This is in line with the new feature to ensure inactive campaigns do not interact with user rewards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 0
Configuration used: CodeRabbit UI
Files ignored due to path filters (2)
Cargo.lock
is excluded by:!**/*.lock
contracts/airdrop/Cargo.toml
is excluded by:!**/*.toml
Files selected for processing (1)
- contracts/airdrop/src/contract.rs (7 hunks)
Files skipped from review as they are similar to previous changes (1)
- contracts/airdrop/src/contract.rs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 0
Configuration used: CodeRabbit UI
Files ignored due to path filters (1)
Cargo.lock
is excluded by:!**/*.lock
Files selected for processing (1)
- contracts/airdrop/src/contract.rs (7 hunks)
Files skipped from review as they are similar to previous changes (1)
- contracts/airdrop/src/contract.rs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 2
Configuration used: CodeRabbit UI
Files selected for processing (4)
- contracts/core-token-vesting/src/contract.rs (6 hunks)
- contracts/core-token-vesting/src/errors.rs (1 hunks)
- contracts/core-token-vesting/src/msg.rs (1 hunks)
- contracts/core-token-vesting/src/state.rs (2 hunks)
Files skipped from review due to trivial changes (1)
- contracts/core-token-vesting/src/errors.rs
Additional comments: 15
contracts/core-token-vesting/src/state.rs (4)
- 8-9: Constants
CAMPAIGN
andUSER_REWARDS
are correctly defined usingMap::new
. This follows the best practice for defining storage maps in CosmWasm contracts.- 23-34: The
Campaign
struct is well-defined with appropriate fields, including the newly addedis_active
field to track the campaign's active status. This addition aligns with the PR objectives. Ensure that all instances ofCampaign
creation or updates in the codebase now include theis_active
field.- 36-39: The
DeregisterResult
struct is correctly defined for use in the deregistration process, encapsulating the messages and attributes to be returned. This struct enhances the modularity and readability of the code.- 40-40: The
denom_to_key
function's logic appears sound, converting aDenom
enum into a string key. This function is crucial for handling different token denominations in storage operations. Ensure that all usages of this function correctly handle the returned key format.contracts/core-token-vesting/src/msg.rs (6)
- 50-64: The
CreateCampaign
variant inExecuteMsg
is correctly defined with appropriate fields for campaign creation. This addition aligns with the PR objectives to enhance campaign management. Ensure that the handling of this message type is implemented correctly in the contract's execute function.- 66-73: The
RewardUsers
variant inExecuteMsg
is well-defined, allowing for rewarding users within a campaign. This change is crucial for the new feature set introduced in the PR. Ensure that the logic for processing these reward requests properly validates the campaign's active status and has adequate error handling.- 75-86: The
DeregisterVestingAccounts
variant inExecuteMsg
introduces functionality for bulk deregistration of vesting accounts. This is a significant improvement for managing vesting accounts more efficiently. Ensure that the implementation properly handles errors and edge cases, such as non-existent accounts or unauthorized attempts.- 88-94: The
ClaimCampaign
andDeactivateCampaign
variants inExecuteMsg
are correctly added to support claiming rewards from a campaign and deactivating campaigns, respectively. These additions are essential for the new functionality described in the PR. Ensure that these operations check for the campaign's active status and ownership correctly.- 96-111: The
Withdraw
variant inExecuteMsg
allows the campaign owner to withdraw unallocated tokens, which is a critical feature for managing campaign funds. Ensure that the implementation includes checks for sufficient unallocated funds and proper authorization.- 114-124: The
RewardUserRequest
andRewardUserResponse
structs are well-defined, facilitating the reward process within campaigns. These structs will help in structuring the reward requests and responses, enhancing the contract's usability and maintainability.contracts/core-token-vesting/src/contract.rs (5)
- 74-93: The
deregister_vesting_account
function is correctly implemented, handling the deregistration logic for a single vesting account. However, ensure that the error handling is consistent and informative, providing clear messages for different failure scenarios.- 97-112: The
create_campaign
function correctly implements the logic for creating a new campaign. It includes validation for campaign uniqueness and proper coin handling. Ensure that the campaign'sis_active
status is set totrue
upon creation, as per the PR objectives.- 114-116: The
reward_users
function is crucial for the new feature set, allowing for the rewarding of users within a campaign. Ensure that this function properly checks the campaign's active status and has adequate error handling for insufficient funds.- 251-293: The
create_campaign
function implementation is correct, ensuring that only one coin type is used for the campaign's funding and validating the campaign's uniqueness. This aligns with the PR objectives for flexible campaign management. Ensure that the campaign'sis_active
status is correctly set totrue
upon creation.- 340-377: The
deregister_vesting_accounts
function correctly implements bulk deregistration logic. This function enhances the contract's functionality by allowing multiple vesting accounts to be deregistered in a single transaction. Ensure that error handling is robust, especially in cases where some deregistrations fail while others succeed.
fn deactivate_campaign( | ||
deps: DepsMut, | ||
env: Env, | ||
info: MessageInfo, | ||
campaign_id: String, | ||
) -> Result<Response, ContractError> { | ||
let mut campaign = CAMPAIGN | ||
.load(deps.storage, campaign_id.clone()) | ||
.map_err(|_| StdError::generic_err("Failed to load campaign data"))?; | ||
|
||
if campaign.owner != info.sender | ||
&& !campaign.managers.contains(&info.sender.to_string()) | ||
{ | ||
return Err(StdError::generic_err("unauthorized").into()); | ||
} | ||
|
||
if !campaign.is_active { | ||
return Ok(Response::new() | ||
.add_attribute("method", "deactivate") | ||
.add_attribute("message", "Campaign is already deactivated")); | ||
} | ||
|
||
campaign.is_active = false; | ||
CAMPAIGN.save(deps.storage, campaign_id.clone(), &campaign)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The deactivate_campaign
function correctly implements the logic for deactivating a campaign. It includes checks for authorization and the campaign's current active status. However, the withdrawal logic at the end seems to attempt to withdraw the entire contract balance, which might not be the intended behavior. Clarify the intention behind this logic.
- return withdraw(deps, env, info, own_balance, campaign_id);
+ // Ensure the withdrawal amount aligns with the campaign's unallocated funds, not the entire contract balance.
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
fn deactivate_campaign( | |
deps: DepsMut, | |
env: Env, | |
info: MessageInfo, | |
campaign_id: String, | |
) -> Result<Response, ContractError> { | |
let mut campaign = CAMPAIGN | |
.load(deps.storage, campaign_id.clone()) | |
.map_err(|_| StdError::generic_err("Failed to load campaign data"))?; | |
if campaign.owner != info.sender | |
&& !campaign.managers.contains(&info.sender.to_string()) | |
{ | |
return Err(StdError::generic_err("unauthorized").into()); | |
} | |
if !campaign.is_active { | |
return Ok(Response::new() | |
.add_attribute("method", "deactivate") | |
.add_attribute("message", "Campaign is already deactivated")); | |
} | |
campaign.is_active = false; | |
CAMPAIGN.save(deps.storage, campaign_id.clone(), &campaign)?; | |
fn deactivate_campaign( | |
deps: DepsMut, | |
env: Env, | |
info: MessageInfo, | |
campaign_id: String, | |
) -> Result<Response, ContractError> { | |
let mut campaign = CAMPAIGN | |
.load(deps.storage, campaign_id.clone()) | |
.map_err(|_| StdError::generic_err("Failed to load campaign data"))?; | |
if campaign.owner != info.sender | |
&& !campaign.managers.contains(&info.sender.to_string()) | |
{ | |
return Err(StdError::generic_err("unauthorized").into()); | |
} | |
if !campaign.is_active { | |
return Ok(Response::new() | |
.add_attribute("method", "deactivate") | |
.add_attribute("message", "Campaign is already deactivated")); | |
} | |
campaign.is_active = false; | |
CAMPAIGN.save(deps.storage, campaign_id.clone(), &campaign)?; | |
// Ensure the withdrawal amount aligns with the campaign's unallocated funds, not the entire contract balance. |
/// Allow the contract owner to withdraw native tokens | ||
/// | ||
/// Ensures the requested amount is available in the contract balance. Transfers | ||
/// tokens to the contract owner's account. | ||
pub fn withdraw( | ||
deps: DepsMut, | ||
env: Env, | ||
info: MessageInfo, | ||
amount: Uint128, | ||
campaign_id: String, | ||
) -> Result<Response, ContractError> { | ||
let campaign = CAMPAIGN.load(deps.storage, campaign_id)?; | ||
|
||
if info.sender != campaign.owner { | ||
return Err( | ||
StdError::generic_err("Only contract owner can withdraw").into() | ||
); | ||
} | ||
|
||
let bond_denom = deps.querier.query_bonded_denom()?; | ||
|
||
let own_balance: Uint128 = deps | ||
.querier | ||
.query_balance(env.contract.address, bond_denom.clone()) | ||
.map_err(|_| { | ||
ContractError::Std(StdError::generic_err( | ||
"Failed to query contract balance", | ||
)) | ||
})? | ||
.amount; | ||
|
||
if amount > own_balance { | ||
return Err( | ||
StdError::generic_err("Not enough funds in the contract").into() | ||
); | ||
} | ||
|
||
let res = Response::new() | ||
.add_attribute("method", "withdraw") | ||
.add_message(CosmosMsg::Bank(BankMsg::Send { | ||
to_address: info.sender.to_string(), | ||
amount: vec![Coin { | ||
denom: bond_denom.clone(), | ||
amount, | ||
}], | ||
})); | ||
|
||
// Update campaign unallocated amount | ||
if amount > campaign.unallocated_amount { | ||
let update_result = CAMPAIGN.update( | ||
deps.storage, | ||
campaign_id, | ||
|mut campaign| -> StdResult<Campaign> { | ||
if let Some(mut campaign) = campaign { | ||
campaign.unallocated_amount = Uint128::zero(); | ||
Ok(campaign) | ||
} else { | ||
Err(StdError::generic_err("Campaign not found")) | ||
} | ||
}, | ||
); | ||
|
||
if let Err(e) = update_result { | ||
return Err(e.into()); | ||
} | ||
} else { | ||
let update_result = CAMPAIGN.update( | ||
deps.storage, | ||
campaign_id, | ||
|mut campaign| -> StdResult<Campaign> { | ||
if let Some(mut campaign) = campaign { | ||
campaign.unallocated_amount -= amount; | ||
Ok(campaign) | ||
} else { | ||
Err(StdError::generic_err("Campaign not found")) | ||
} | ||
}, | ||
); | ||
|
||
if let Err(e) = update_result { | ||
return Err(e.into()); | ||
} | ||
} | ||
Err(StdError::generic_err("Campaign not found"))?; | ||
|
||
return Ok(res); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The withdraw
function correctly implements the logic for withdrawing unallocated tokens from a campaign. However, the error handling at the end seems misplaced, as it will always trigger an error regardless of successful execution. This appears to be a mistake and should be removed to allow the function to return a successful response.
- Err(StdError::generic_err("Campaign not found"))?;
- return Ok(res);
+ Ok(res)
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
/// Allow the contract owner to withdraw native tokens | |
/// | |
/// Ensures the requested amount is available in the contract balance. Transfers | |
/// tokens to the contract owner's account. | |
pub fn withdraw( | |
deps: DepsMut, | |
env: Env, | |
info: MessageInfo, | |
amount: Uint128, | |
campaign_id: String, | |
) -> Result<Response, ContractError> { | |
let campaign = CAMPAIGN.load(deps.storage, campaign_id)?; | |
if info.sender != campaign.owner { | |
return Err( | |
StdError::generic_err("Only contract owner can withdraw").into() | |
); | |
} | |
let bond_denom = deps.querier.query_bonded_denom()?; | |
let own_balance: Uint128 = deps | |
.querier | |
.query_balance(env.contract.address, bond_denom.clone()) | |
.map_err(|_| { | |
ContractError::Std(StdError::generic_err( | |
"Failed to query contract balance", | |
)) | |
})? | |
.amount; | |
if amount > own_balance { | |
return Err( | |
StdError::generic_err("Not enough funds in the contract").into() | |
); | |
} | |
let res = Response::new() | |
.add_attribute("method", "withdraw") | |
.add_message(CosmosMsg::Bank(BankMsg::Send { | |
to_address: info.sender.to_string(), | |
amount: vec![Coin { | |
denom: bond_denom.clone(), | |
amount, | |
}], | |
})); | |
// Update campaign unallocated amount | |
if amount > campaign.unallocated_amount { | |
let update_result = CAMPAIGN.update( | |
deps.storage, | |
campaign_id, | |
|mut campaign| -> StdResult<Campaign> { | |
if let Some(mut campaign) = campaign { | |
campaign.unallocated_amount = Uint128::zero(); | |
Ok(campaign) | |
} else { | |
Err(StdError::generic_err("Campaign not found")) | |
} | |
}, | |
); | |
if let Err(e) = update_result { | |
return Err(e.into()); | |
} | |
} else { | |
let update_result = CAMPAIGN.update( | |
deps.storage, | |
campaign_id, | |
|mut campaign| -> StdResult<Campaign> { | |
if let Some(mut campaign) = campaign { | |
campaign.unallocated_amount -= amount; | |
Ok(campaign) | |
} else { | |
Err(StdError::generic_err("Campaign not found")) | |
} | |
}, | |
); | |
if let Err(e) = update_result { | |
return Err(e.into()); | |
} | |
} | |
Err(StdError::generic_err("Campaign not found"))?; | |
return Ok(res); | |
} | |
/// Allow the contract owner to withdraw native tokens | |
/// | |
/// Ensures the requested amount is available in the contract balance. Transfers | |
/// tokens to the contract owner's account. | |
pub fn withdraw( | |
deps: DepsMut, | |
env: Env, | |
info: MessageInfo, | |
amount: Uint128, | |
campaign_id: String, | |
) -> Result<Response, ContractError> { | |
let campaign = CAMPAIGN.load(deps.storage, campaign_id)?; | |
if info.sender != campaign.owner { | |
return Err( | |
StdError::generic_err("Only contract owner can withdraw").into() | |
); | |
} | |
let bond_denom = deps.querier.query_bonded_denom()?; | |
let own_balance: Uint128 = deps | |
.querier | |
.query_balance(env.contract.address, bond_denom.clone()) | |
.map_err(|_| { | |
ContractError::Std(StdError::generic_err( | |
"Failed to query contract balance", | |
)) | |
})? | |
.amount; | |
if amount > own_balance { | |
return Err( | |
StdError::generic_err("Not enough funds in the contract").into() | |
); | |
} | |
let res = Response::new() | |
.add_attribute("method", "withdraw") | |
.add_message(CosmosMsg::Bank(BankMsg::Send { | |
to_address: info.sender.to_string(), | |
amount: vec![Coin { | |
denom: bond_denom.clone(), | |
amount, | |
}], | |
})); | |
// Update campaign unallocated amount | |
if amount > campaign.unallocated_amount { | |
let update_result = CAMPAIGN.update( | |
deps.storage, | |
campaign_id, | |
|mut campaign| -> StdResult<Campaign> { | |
if let Some(mut campaign) = campaign { | |
campaign.unallocated_amount = Uint128::zero(); | |
Ok(campaign) | |
} else { | |
Err(StdError::generic_err("Campaign not found")) | |
} | |
}, | |
); | |
if let Err(e) = update_result { | |
return Err(e.into()); | |
} | |
} else { | |
let update_result = CAMPAIGN.update( | |
deps.storage, | |
campaign_id, | |
|mut campaign| -> StdResult<Campaign> { | |
if let Some(mut campaign) = campaign { | |
campaign.unallocated_amount -= amount; | |
Ok(campaign) | |
} else { | |
Err(StdError::generic_err("Campaign not found")) | |
} | |
}, | |
); | |
if let Err(e) = update_result { | |
return Err(e.into()); | |
} | |
} | |
Ok(res) | |
} |
Summary by CodeRabbit