Skip to content
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

Closed
wants to merge 6 commits into from

Conversation

matthiasmatt
Copy link
Contributor

@matthiasmatt matthiasmatt commented Jan 10, 2024

Summary by CodeRabbit

  • New Features
    • Introduced the ability to deactivate airdrop campaigns.
    • Added functionality to reward users within active campaigns.
    • Implemented features for users to claim and withdraw their rewards.
    • Enhanced the campaign creation process with more options.
  • Bug Fixes
    • Ensured that rewards can only be claimed or withdrawn from active campaigns.
  • Refactor
    • Improved code readability and consistency across various modules.
    • Updated function logic and signatures to support new features.
  • Tests
    • Added new tests and modified existing ones to cover the changes in campaign activity checks and withdrawal logic.

Copy link
Contributor

coderabbitai bot commented Jan 10, 2024

Walkthrough

The update introduces significant enhancements to an airdrop contract, integrating a new is_active field for campaign management, a mechanism to deactivate campaigns, and comprehensive adjustments across various functions for improved functionality. This includes new request and response types for user rewards, enhanced logic for handling multiple addresses in vesting account deregistration, and updates to campaign creation and withdrawal processes. The modifications aim to refine the contract's operational efficiency and user interaction.

Changes

Files Change Summary
contracts/airdrop/src/contract.rs Introduced is_active field, DeactivateCampaign, campaign activity checks, and various function updates.
contracts/airdrop/src/msg.rs Improved import order and enum field declarations.
contracts/airdrop/src/tests/execute/... Updated tests with new imports, function calls, and logic reflecting contract changes.
contracts/core-token-vesting/src/... (various) Added new functionalities for campaign management, user rewards, and vesting account operations.

"In the realm of code, where logic trees grow,
🐇 A rabbit hopped in, making updates flow.
With a flick and a hop, new features align,
In contracts and tests, improvements shine."
🌟📜🎉

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?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit-tests for this file.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between dd90c9a and e3b524f.
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 the Campaign struct is a significant change. Ensure that all functionalities that interact with the Campaign 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 the Campaign struct instantiation within the test is correct. This ensures that the tests will now take into account the new is_active field.
contracts/airdrop/src/tests/instantiate.rs (3)
  • 34-34: The addition of the is_active field in the Campaign struct instantiation within the test is correct. This ensures that the tests will now take into account the new is_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 the is_active field in the Campaign struct. This ensures that the tests will now take into account the new is_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 new is_active logic.

contracts/airdrop/src/contract.rs (6)
  • 45-45: The instantiate function correctly initializes the is_active field to true 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 the ExecuteMsg 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,
Copy link
Contributor

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.

Comment on lines 100 to 102
// if i desactivate the campaign, everything should be withdrawn
let resp = desactivate(deps.as_mut(), env.clone(), mock_info("owner", &[]))
.unwrap();
Copy link
Contributor

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.

Copy link

codecov bot commented Jan 10, 2024

Codecov Report

Attention: 35 lines in your changes are missing coverage. Please review.

Comparison is base (902b788) 85.16% compared to head (24d8783) 85.16%.
Report is 3 commits behind head on main.

❗ Current head 24d8783 differs from pull request most recent head 8cb28a6. Consider uploading reports for the commit 8cb28a6 to get more accurate results

Additional details and impacted files
Files Coverage Δ
contracts/airdrop/src/msg.rs 40.00% <ø> (ø)
contracts/airdrop/src/state.rs 100.00% <ø> (ø)
nibiru-std/src/proto/type_url_nibiru.rs 95.00% <ø> (ø)
packages/bash-rs/src/errors.rs 0.00% <ø> (ø)
packages/core-controller/src/msgs.rs 33.33% <ø> (ø)
nibiru-std/src/math.rs 80.45% <0.00%> (-0.40%) ⬇️
packages/core-controller/src/contract.rs 88.18% <0.00%> (ø)
scripts/wasm_out.rs 62.01% <0.00%> (ø)
contracts/airdrop/src/contract.rs 74.05% <83.56%> (+2.47%) ⬆️
packages/bash-rs/src/bash.rs 59.45% <33.33%> (-7.72%) ⬇️

@@ -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),
Copy link
Contributor

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between e3b524f and 73a39c7.
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 the Campaign 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 the ExecuteMsg 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.

Comment on lines 204 to 206
if !campaign.is_active {
return Err(StdError::generic_err("Campaign is not active"));
}
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 10, 2024

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.

Suggested change
if !campaign.is_active {
return Err(StdError::generic_err("Campaign is not active"));
}

Copy link
Member

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

Copy link
Contributor

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.

Comment on lines 204 to 206
if !campaign.is_active {
return Err(StdError::generic_err("Campaign is not active"));
}
Copy link
Member

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between 73a39c7 and 7729126.
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 the Campaign 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 the ExecuteMsg 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 the is_active flag to false. The function ends with a call to withdraw, 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 the contract.rs file, confirming that the withdrawal of all funds is the expected behavior when a campaign is deactivated.

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 10

Length 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 -p

Length of output: 13505

* 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.
  • 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between 7729126 and 15ef7f4.
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

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between 15ef7f4 and 24d8783.
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

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between 24d8783 and 8cb28a6.
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 and USER_REWARDS are correctly defined using Map::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 added is_active field to track the campaign's active status. This addition aligns with the PR objectives. Ensure that all instances of Campaign creation or updates in the codebase now include the is_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 a Denom 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 in ExecuteMsg 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 in ExecuteMsg 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 in ExecuteMsg 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 and DeactivateCampaign variants in ExecuteMsg 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 in ExecuteMsg 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 and RewardUserResponse 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's is_active status is set to true 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's is_active status is correctly set to true 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.

Comment on lines +144 to +167
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)?;
Copy link
Contributor

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.

Suggested change
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.

Comment on lines +654 to +740
/// 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);
}
Copy link
Contributor

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.

Suggested change
/// 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)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ✅ Completed
Development

Successfully merging this pull request may close these issues.

3 participants