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

feature: optional withdrawal address #149

Merged
merged 9 commits into from
Dec 29, 2023

Conversation

taitruong
Copy link
Collaborator

This feature adds an optional withdrawal address. Please note: existing 3rd party doesnt need to update. Like it is still possible using old instantiate msg without defining a withdraw address!

This PR allows current NFT contracts to:

  1. set and remove withdraw addresses by owner
  2. withdraw funds by any to given address

This way launchpads, fee sharing, marketplaces etc. can benefit from this, like:

  • by sending funds or royalties to nft contract where a creator on the other can withdraw them
  • define other 3rd party contracts to withdraw funds

Added these new messages to cw721-base:

ExecuteMsg:

    /// Sets address to send withdrawn fees to. Only owner can call this.
    SetWithdrawAddress { address: String },
    /// Removes the withdraw address, so fees are sent to the contract. Only owner can call this.
    RemoveWithdrawAddress {},
    /// Withdraw from the contract to the given address. Anyone can call this,
    /// which is okay since withdraw address has been set by owner.
    WithdrawFunds { amount: Coin },

QueryMsg:

    #[returns(String)]
    GetWithdrawAddress {},

Comment on lines +75 to +81
/// Sets address to send withdrawn fees to. Only owner can call this.
SetWithdrawAddress { address: String },
/// Removes the withdraw address, so fees are sent to the contract. Only owner can call this.
RemoveWithdrawAddress {},
/// Withdraw from the contract to the given address. Anyone can call this,
/// which is okay since withdraw address has been set by owner.
WithdrawFunds { amount: Coin },
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Execute msgs for new feature here

Comment on lines +171 to +172
#[returns(Option<String>)]
GetWithdrawAddress {},
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query here

@@ -19,6 +19,7 @@ where
/// Stored as (granter, operator) giving operator full control over granter's account
pub operators: Map<'a, (&'a Addr, &'a Addr), Expiration>,
pub tokens: IndexedMap<'a, &'a str, TokenInfo<T>, TokenIndexes<'a, T>>,
pub withdraw_address: Item<'a, String>,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new state here

Comment on lines +145 to +157
pub fn set_withdraw_address(
&self,
deps: DepsMut,
sender: &Addr,
address: String,
) -> Result<Response<C>, ContractError> {
cw_ownable::assert_owner(deps.storage, sender)?;
deps.api.addr_validate(&address)?;
self.withdraw_address.save(deps.storage, &address)?;
Ok(Response::new()
.add_attribute("action", "set_withdraw_address")
.add_attribute("address", address))
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only owner (aka creator/minter) can set withdraw address

Comment on lines +159 to +175
pub fn remove_withdraw_address(
&self,
storage: &mut dyn Storage,
sender: &Addr,
) -> Result<Response<C>, ContractError> {
cw_ownable::assert_owner(storage, sender)?;
let address = self.withdraw_address.may_load(storage)?;
match address {
Some(address) => {
self.withdraw_address.remove(storage);
Ok(Response::new()
.add_attribute("action", "remove_withdraw_address")
.add_attribute("address", address))
}
None => Err(ContractError::NoWithdrawAddress {}),
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only owner can remove withdraw address

Comment on lines +909 to +926
fn test_withdraw_funds() {
let mut deps = mock_dependencies();
let contract = setup_contract(deps.as_mut());

// no withdraw address set
let err = contract
.withdraw_funds(deps.as_mut().storage, &Coin::new(100, "uark"))
.unwrap_err();
assert_eq!(err, ContractError::NoWithdrawAddress {});

// set and withdraw by non-owner
contract
.set_withdraw_address(deps.as_mut(), &Addr::unchecked(MINTER), "foo".to_string())
.unwrap();
contract
.withdraw_funds(deps.as_mut().storage, &Coin::new(100, "uark"))
.unwrap();
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test withdraw_funds()

Comment on lines +137 to +168
/// Test backward compatibility using instantiate msg from a 0.16 version on latest contract.
/// This ensures existing 3rd party contracts doesnt need to updated as well.
#[test]
fn test_instantiate_016_msg() {
use cw721_base_016 as v16;
let mut app = App::default();
let admin = || Addr::unchecked("admin");

let code_id_latest = app.store_code(cw721_base_latest_contract());

let cw721 = app
.instantiate_contract(
code_id_latest,
admin(),
&v16::InstantiateMsg {
name: "collection".to_string(),
symbol: "symbol".to_string(),
minter: admin().into_string(),
},
&[],
"cw721-base",
Some(admin().into_string()),
)
.unwrap();

// assert withdraw address is None
let withdraw_addr: Option<String> = app
.wrap()
.query_wasm_smart(cw721, &crate::QueryMsg::<Empty>::GetWithdrawAddress {})
.unwrap();
assert!(withdraw_addr.is_none());
}
Copy link
Collaborator Author

@taitruong taitruong Dec 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test backward compatibility using instantiate msg from a 0.16 version on latest contract. This ensures existing 3rd party contracts doesnt need to be updated.
Basically NFT contracts behaves like in previous version - this PR just adds an optional feature.

@@ -14,6 +14,7 @@ pub struct InstantiateMsg {
pub cw20_address: Addr,
pub token_uri: String,
pub extension: Extension,
pub withdraw_address: Option<String>,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional as well on fix price contract

@@ -93,6 +93,7 @@ mod tests {
name: "".into(),
symbol: "".into(),
minter: "larry".into(),
withdraw_address: None,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional as well on metadata onchain contract

@@ -47,6 +47,7 @@ pub mod entry {
name: msg.name,
symbol: msg.symbol,
minter: msg.minter,
withdraw_address: msg.withdraw_address,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional as well on non transferrable contract

Copy link
Collaborator

@JakeHartnell JakeHartnell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm cool with this, but would like to make sure there is broad consensus before we move forward. Otherwise, LGTM.

@taitruong
Copy link
Collaborator Author

taitruong commented Dec 28, 2023

@shanev could u have a look at this? PR allows now NFT contracts having funds. Owner can set/remove withdraw address - which is used for WithdrawFunds msg.

withdraw_address is optional and backward compatible.

Copy link
Collaborator

@shanev shanev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some minor comments.

cw20 = "1.1.0"
cw721 = { version = "0.18.0", path = "./packages/cw721" }
cw721-base = { version = "0.18.0", path = "./contracts/cw721-base" }
cosmwasm-schema = "^1.2"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version numbers should be explicit for smart contracts.

@@ -21,6 +21,7 @@ command = "cargo"
args = ["build", "--release", "--locked", "--target", "wasm32-unknown-unknown"]

[tasks.optimize]
# https://hub.docker.com/r/cosmwasm/workspace-optimizer/tags https://hub.docker.com/r/cosmwasm/workspace-optimizer-arm64/tags
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this mean't to be commented out?

@@ -157,6 +167,9 @@ pub enum QueryMsg<Q: JsonSchema> {
/// Extension query
#[returns(())]
Extension { msg: Q },

#[returns(Option<String>)]
GetWithdrawAddress {},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer not having explicit getter in name per Rust convention, so just WithdrawAddress {}. Developer discretion.

@taitruong taitruong merged commit cc27e27 into CosmWasm:main Dec 29, 2023
6 checks passed
@taitruong taitruong mentioned this pull request Jan 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants