From 305576987a0ccccdf23795350bbd90a69243d42b Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Wed, 10 Jul 2024 19:53:44 -0700 Subject: [PATCH 01/31] add cm authority as additional delegate --- programs/candy-guard/Cargo.lock | 108 +++++++++++++----- programs/candy-guard/program/Cargo.toml | 2 +- .../program/src/guards/asset_burn.rs | 4 +- .../program/src/guards/asset_burn_multi.rs | 6 +- .../program/src/guards/asset_mint_limit.rs | 4 +- .../program/src/guards/asset_payment.rs | 4 +- .../program/src/guards/asset_payment_multi.rs | 6 +- .../candy-guard/program/src/guards/mod.rs | 6 +- programs/candy-machine-core/Cargo.lock | 106 ++++++++++++----- .../candy-machine-core/program/Cargo.toml | 2 +- .../program/src/instructions/mint_asset.rs | 8 +- .../candy-machine-core/program/src/utils.rs | 37 +++--- 12 files changed, 206 insertions(+), 87 deletions(-) diff --git a/programs/candy-guard/Cargo.lock b/programs/candy-guard/Cargo.lock index 06dc934..ecd3786 100644 --- a/programs/candy-guard/Cargo.lock +++ b/programs/candy-guard/Cargo.lock @@ -609,7 +609,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -801,7 +801,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -812,7 +812,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1231,16 +1231,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "mpl-core" -version = "0.6.1" +version = "0.8.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d0b8184f1f86f01939559f24d0aa27071752b344ae8136234f0ab4887dbf0a" +checksum = "83f55ea55e53c13045196f36358dc841f583697d71dc6d68775ad57f98afbb68" dependencies = [ "base64 0.22.0", "borsh 0.10.3", + "modular-bitfield", "num-derive 0.3.3", "num-traits", + "rmp-serde", + "serde_json", "solana-program", "thiserror", ] @@ -1337,7 +1361,7 @@ checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1387,7 +1411,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1494,9 +1518,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1512,9 +1536,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1658,6 +1682,28 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1699,9 +1745,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.183" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -1717,13 +1763,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1756,7 +1802,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1869,7 +1915,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -2014,7 +2060,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -2081,7 +2127,7 @@ checksum = "b4fa8f409b5c5e0ac571df17c981ae1424b204743daa4428430627d38717caf5" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -2093,7 +2139,7 @@ dependencies = [ "proc-macro2", "quote", "solana-program", - "syn 2.0.28", + "syn 2.0.70", "thiserror", ] @@ -2127,7 +2173,7 @@ checksum = "173f3cc506847882189b3a5b67299f617fed2f9730f122dd197b82e1e213dee5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -2207,6 +2253,12 @@ dependencies = [ "spl-program-error", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2232,9 +2284,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" dependencies = [ "proc-macro2", "quote", @@ -2267,7 +2319,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -2416,7 +2468,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", "wasm-bindgen-shared", ] @@ -2438,7 +2490,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2573,5 +2625,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] diff --git a/programs/candy-guard/program/Cargo.toml b/programs/candy-guard/program/Cargo.toml index a628d05..44f265a 100644 --- a/programs/candy-guard/program/Cargo.toml +++ b/programs/candy-guard/program/Cargo.toml @@ -23,7 +23,7 @@ arrayref = "0.3.6" mpl-core-candy-guard-derive = { path = "../macro", version = "0.2.1" } mpl-core-candy-machine-core = { path = "../../candy-machine-core/program", version = "0.2.0", features = ["cpi"] } mpl-token-metadata = "3.2.1" -mpl-core = { version = "0.6.1" } +mpl-core = { version = "0.8.0-alpha.2" } solana-program = "~1.16.5" spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } spl-token = { version = ">= 3.5.0, < 5.0", features = ["no-entrypoint"] } diff --git a/programs/candy-guard/program/src/guards/asset_burn.rs b/programs/candy-guard/program/src/guards/asset_burn.rs index 241a819..d90a1b5 100644 --- a/programs/candy-guard/program/src/guards/asset_burn.rs +++ b/programs/candy-guard/program/src/guards/asset_burn.rs @@ -42,8 +42,8 @@ impl Condition for AssetBurn { assert_keys_equal(&collection_info.key(), &self.required_collection) .map_err(|_| CandyGuardError::InvalidNftCollection)?; - let asset = Asset::try_from(asset_info)?; - assert_keys_equal(&asset.base.owner, ctx.accounts.minter.key) + let asset = BaseAssetV1::try_from(asset_info)?; + assert_keys_equal(&asset.owner, ctx.accounts.minter.key) .map_err(|_| CandyGuardError::IncorrectOwner)?; ctx.indices.insert("asset_burn_index", index); diff --git a/programs/candy-guard/program/src/guards/asset_burn_multi.rs b/programs/candy-guard/program/src/guards/asset_burn_multi.rs index 582562d..f449565 100644 --- a/programs/candy-guard/program/src/guards/asset_burn_multi.rs +++ b/programs/candy-guard/program/src/guards/asset_burn_multi.rs @@ -45,9 +45,9 @@ impl Condition for AssetBurnMulti { while i < usize::from(self.num) { asset_account = try_get_account_info(ctx.accounts.remaining, index + i + 1)?; - asset = Asset::try_from(asset_account)?; + asset = BaseAssetV1::try_from(asset_account)?; - match asset.base.update_authority { + match asset.update_authority { UpdateAuthority::Collection(pubkey) => { assert_keys_equal(&pubkey, collection_account.key) .map_err(|_| CandyGuardError::InvalidNftCollection)?; @@ -57,7 +57,7 @@ impl Condition for AssetBurnMulti { assert_keys_equal(collection_account.key, &self.required_collection) .map_err(|_| CandyGuardError::InvalidNftCollection)?; - assert_keys_equal(&asset.base.owner, ctx.accounts.minter.key) + assert_keys_equal(&asset.owner, ctx.accounts.minter.key) .map_err(|_| CandyGuardError::IncorrectOwner)?; i += 1; diff --git a/programs/candy-guard/program/src/guards/asset_mint_limit.rs b/programs/candy-guard/program/src/guards/asset_mint_limit.rs index d8a927e..b50058c 100644 --- a/programs/candy-guard/program/src/guards/asset_mint_limit.rs +++ b/programs/candy-guard/program/src/guards/asset_mint_limit.rs @@ -78,8 +78,8 @@ impl Condition for AssetMintLimit { // verifies that we got the correct Core Asset verify_core_collection(asset_account, &self.required_collection)?; - let asset = Asset::try_from(asset_account)?; - if assert_keys_equal(&asset.base.owner, ctx.accounts.minter.key).is_err() { + let asset = BaseAssetV1::try_from(asset_account)?; + if assert_keys_equal(&asset.owner, ctx.accounts.minter.key).is_err() { return err!(CandyGuardError::IncorrectOwner); } diff --git a/programs/candy-guard/program/src/guards/asset_payment.rs b/programs/candy-guard/program/src/guards/asset_payment.rs index 3ffd639..d1dc6a8 100644 --- a/programs/candy-guard/program/src/guards/asset_payment.rs +++ b/programs/candy-guard/program/src/guards/asset_payment.rs @@ -50,8 +50,8 @@ impl Condition for AssetPayment { assert_keys_equal(&collection_info.key(), &self.required_collection) .map_err(|_| CandyGuardError::InvalidNftCollection)?; - let asset = Asset::try_from(asset_info)?; - assert_keys_equal(&asset.base.owner, ctx.accounts.minter.key) + let asset = BaseAssetV1::try_from(asset_info)?; + assert_keys_equal(&asset.owner, ctx.accounts.minter.key) .map_err(|_| CandyGuardError::IncorrectOwner)?; ctx.indices.insert("asset_payment_index", index); diff --git a/programs/candy-guard/program/src/guards/asset_payment_multi.rs b/programs/candy-guard/program/src/guards/asset_payment_multi.rs index 429798d..eda3844 100644 --- a/programs/candy-guard/program/src/guards/asset_payment_multi.rs +++ b/programs/candy-guard/program/src/guards/asset_payment_multi.rs @@ -51,8 +51,8 @@ impl Condition for AssetPaymentMulti { while i < usize::from(self.num) { asset_account = try_get_account_info(ctx.accounts.remaining, index + i + 2)?; - asset = Asset::try_from(asset_account)?; - match asset.base.update_authority { + asset = BaseAssetV1::try_from(asset_account)?; + match asset.update_authority { UpdateAuthority::Collection(pubkey) => { assert_keys_equal(&pubkey, collection_account.key) .map_err(|_| CandyGuardError::InvalidNftCollection)?; @@ -62,7 +62,7 @@ impl Condition for AssetPaymentMulti { assert_keys_equal(collection_account.key, &self.required_collection) .map_err(|_| CandyGuardError::InvalidNftCollection)?; - assert_keys_equal(&asset.base.owner, ctx.accounts.minter.key) + assert_keys_equal(&asset.owner, ctx.accounts.minter.key) .map_err(|_| CandyGuardError::IncorrectOwner)?; i += 1; diff --git a/programs/candy-guard/program/src/guards/mod.rs b/programs/candy-guard/program/src/guards/mod.rs index b6a81d0..4bbb7e5 100644 --- a/programs/candy-guard/program/src/guards/mod.rs +++ b/programs/candy-guard/program/src/guards/mod.rs @@ -2,8 +2,8 @@ use std::collections::BTreeMap; pub use anchor_lang::prelude::*; use mpl_core::{ + accounts::BaseAssetV1, types::{PluginAuthorityPair, UpdateAuthority}, - Asset, }; pub use crate::{errors::CandyGuardError, state::GuardSet}; @@ -217,9 +217,9 @@ pub fn get_account_info(remaining_accounts: &[T], index: usize) -> Option<&T> } pub fn verify_core_collection(asset: &AccountInfo, collection: &Pubkey) -> Result<()> { - let asset = Asset::try_from(asset)?; + let asset = BaseAssetV1::try_from(asset)?; - match asset.base.update_authority { + match asset.update_authority { UpdateAuthority::Collection(pubkey) => { assert_keys_equal(&pubkey, collection) .map_err(|_| CandyGuardError::InvalidNftCollection)?; diff --git a/programs/candy-machine-core/Cargo.lock b/programs/candy-machine-core/Cargo.lock index d450869..719b9d1 100644 --- a/programs/candy-machine-core/Cargo.lock +++ b/programs/candy-machine-core/Cargo.lock @@ -609,7 +609,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -801,7 +801,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -812,7 +812,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1231,16 +1231,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "mpl-core" -version = "0.6.1" +version = "0.8.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d0b8184f1f86f01939559f24d0aa27071752b344ae8136234f0ab4887dbf0a" +checksum = "83f55ea55e53c13045196f36358dc841f583697d71dc6d68775ad57f98afbb68" dependencies = [ "base64 0.22.0", "borsh 0.10.3", + "modular-bitfield", "num-derive", "num-traits", + "rmp-serde", + "serde_json", "solana-program", "thiserror", ] @@ -1351,7 +1375,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1458,9 +1482,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1476,9 +1500,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1622,6 +1646,28 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1663,9 +1709,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.183" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -1681,13 +1727,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1720,7 +1766,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1833,7 +1879,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -1965,7 +2011,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -2032,7 +2078,7 @@ checksum = "b4fa8f409b5c5e0ac571df17c981ae1424b204743daa4428430627d38717caf5" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -2044,7 +2090,7 @@ dependencies = [ "proc-macro2", "quote", "solana-program", - "syn 2.0.28", + "syn 2.0.70", "thiserror", ] @@ -2078,7 +2124,7 @@ checksum = "173f3cc506847882189b3a5b67299f617fed2f9730f122dd197b82e1e213dee5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -2158,6 +2204,12 @@ dependencies = [ "spl-program-error", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2183,9 +2235,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" dependencies = [ "proc-macro2", "quote", @@ -2218,7 +2270,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] [[package]] @@ -2367,7 +2419,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", "wasm-bindgen-shared", ] @@ -2389,7 +2441,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2524,5 +2576,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.70", ] diff --git a/programs/candy-machine-core/program/Cargo.toml b/programs/candy-machine-core/program/Cargo.toml index 5a4488f..ca24c69 100644 --- a/programs/candy-machine-core/program/Cargo.toml +++ b/programs/candy-machine-core/program/Cargo.toml @@ -21,7 +21,7 @@ anchor-lang = "0.28.0" arrayref = "0.3.6" mpl-token-metadata = "3.2.1" mpl-utils = { version = "0.3", default-features = false } -mpl-core = { version = "0.6.1" } +mpl-core = { version = "0.8.0-alpha.2" } solana-program = "~1.16.5" spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } spl-token = { version = ">= 3.5.0, < 5.0", features = ["no-entrypoint"] } diff --git a/programs/candy-machine-core/program/src/instructions/mint_asset.rs b/programs/candy-machine-core/program/src/instructions/mint_asset.rs index bdf606e..289f54c 100644 --- a/programs/candy-machine-core/program/src/instructions/mint_asset.rs +++ b/programs/candy-machine-core/program/src/instructions/mint_asset.rs @@ -82,12 +82,16 @@ pub(crate) fn process_mint_asset( return err!(CandyError::IncorrectOwner); } - let (auth, _, _) = fetch_plugin::( + let (auth, update_delegate_plugin, _) = fetch_plugin::( &accounts.collection, PluginType::UpdateDelegate, )?; - assert_plugin_pubkey_authority(&auth, &accounts.authority_pda.key())?; + assert_plugin_pubkey_authority( + &auth, + &update_delegate_plugin, + &accounts.authority_pda.key(), + )?; // (2) selecting an item to mint diff --git a/programs/candy-machine-core/program/src/utils.rs b/programs/candy-machine-core/program/src/utils.rs index ba8e39f..6e2151b 100644 --- a/programs/candy-machine-core/program/src/utils.rs +++ b/programs/candy-machine-core/program/src/utils.rs @@ -4,8 +4,8 @@ use mpl_core::{ accounts::BaseCollectionV1, fetch_plugin, instructions::{ - AddCollectionPluginV1CpiBuilder, ApproveCollectionPluginAuthorityV1CpiBuilder, - RevokeCollectionPluginAuthorityV1CpiBuilder, + AddCollectionPluginV1CpiBuilder, RevokeCollectionPluginAuthorityV1CpiBuilder, + UpdateCollectionPluginV1CpiBuilder, }, types::{Plugin, PluginAuthority, PluginType, UpdateDelegate}, }; @@ -283,11 +283,16 @@ pub fn revoke_collection_authority_helper( } } -pub fn assert_plugin_pubkey_authority(auth: &PluginAuthority, authority: &Pubkey) -> Result<()> { +pub fn assert_plugin_pubkey_authority( + auth: &PluginAuthority, + plugin: &UpdateDelegate, + authority: &Pubkey, +) -> Result<()> { if (*auth == PluginAuthority::Address { address: *authority, }) + || plugin.additional_delegates.contains(authority) { Ok(()) } else { @@ -316,23 +321,29 @@ pub fn approve_asset_collection_delegate( } // add CM authority to collection if it doesn't exist - let (auth, _, _) = fetch_plugin::( + let (_, update_plugin, _) = fetch_plugin::( &accounts.collection, PluginType::UpdateDelegate, )?; - let auth_to_add = PluginAuthority::Address { - address: accounts.authority_pda.key(), - }; - if auth_to_add != auth { - ApproveCollectionPluginAuthorityV1CpiBuilder::new(&accounts.mpl_core_program) + + if !update_plugin + .additional_delegates + .contains(accounts.authority_pda.key) + { + // add CM authority as an additional delegate + let mut new_auths = update_plugin.additional_delegates.clone(); + new_auths.push(accounts.authority_pda.key()); + + UpdateCollectionPluginV1CpiBuilder::new(&accounts.mpl_core_program) .collection(&accounts.collection) .authority(Some(&accounts.collection_update_authority)) - .plugin_type(PluginType::UpdateDelegate) - .new_authority(auth_to_add) + .plugin(Plugin::UpdateDelegate(UpdateDelegate { + additional_delegates: new_auths, + })) .system_program(&accounts.system_program) .payer(&accounts.payer) - .invoke() - .map_err(|error| error.into()) + .invoke()?; + Ok(()) } else { Ok(()) } From da7570144e1cf1d5fc9995b686a2140c25855ac1 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Wed, 10 Jul 2024 20:01:00 -0700 Subject: [PATCH 02/31] map error --- programs/candy-machine-core/program/src/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/candy-machine-core/program/src/utils.rs b/programs/candy-machine-core/program/src/utils.rs index 6e2151b..3a78c97 100644 --- a/programs/candy-machine-core/program/src/utils.rs +++ b/programs/candy-machine-core/program/src/utils.rs @@ -342,8 +342,8 @@ pub fn approve_asset_collection_delegate( })) .system_program(&accounts.system_program) .payer(&accounts.payer) - .invoke()?; - Ok(()) + .invoke() + .map_err(|error| error.into()) } else { Ok(()) } From d35f2bc6b9b998c84e4b281857534005424b9ab3 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Fri, 26 Jul 2024 12:28:11 +0000 Subject: [PATCH 03/31] Deploy JS client v0.2.2 --- clients/js/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/js/package.json b/clients/js/package.json index a83b5fe..79d6d7f 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -1,6 +1,6 @@ { "name": "@metaplex-foundation/mpl-core-candy-machine", - "version": "0.2.1", + "version": "0.2.2", "description": "Client library for Candy Machine related programs", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -70,4 +70,4 @@ } }, "packageManager": "pnpm@8.2.0" -} \ No newline at end of file +} From 39c01ae36e17638b779b7619718dd556b4830974 Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Wed, 7 Aug 2024 22:05:53 -0400 Subject: [PATCH 04/31] Add asset gate guard. --- clients/js/src/defaultGuards/assetGate.ts | 34 +++ clients/js/src/defaultGuards/default.ts | 7 + clients/js/src/defaultGuards/index.ts | 1 + clients/js/src/generated/types/assetGate.ts | 32 ++ clients/js/src/generated/types/guardType.ts | 1 + clients/js/src/generated/types/index.ts | 1 + clients/js/src/plugin.ts | 4 +- .../js/test/defaultGuards/assetGate.test.ts | 288 ++++++++++++++++++ idls/candy_guard.json | 33 ++ .../program/src/guards/asset_gate.rs | 66 ++++ .../candy-guard/program/src/guards/mod.rs | 2 + .../program/src/state/candy_guard.rs | 3 + 12 files changed, 471 insertions(+), 1 deletion(-) create mode 100644 clients/js/src/defaultGuards/assetGate.ts create mode 100644 clients/js/src/generated/types/assetGate.ts create mode 100644 clients/js/test/defaultGuards/assetGate.test.ts create mode 100644 programs/candy-guard/program/src/guards/asset_gate.rs diff --git a/clients/js/src/defaultGuards/assetGate.ts b/clients/js/src/defaultGuards/assetGate.ts new file mode 100644 index 0000000..e0878a6 --- /dev/null +++ b/clients/js/src/defaultGuards/assetGate.ts @@ -0,0 +1,34 @@ +import { PublicKey } from '@metaplex-foundation/umi'; +import { getAssetGateSerializer, AssetGate, AssetGateArgs } from '../generated'; +import { GuardManifest, noopParser } from '../guards'; + +/** + * The assetGate guard restricts minting to holders + * of a specified NFT collection. + * + * This means the mint address of an NFT from this + * collection must be passed when minting. + */ +export const assetGateGuardManifest: GuardManifest< + AssetGateArgs, + AssetGate, + AssetGateMintArgs +> = { + name: 'assetGate', + serializer: getAssetGateSerializer, + mintParser: (context, mintContext, args) => ({ + data: new Uint8Array(), + remainingAccounts: [ + { publicKey: args.asset, isWritable: false }, + ], + }), + routeParser: noopParser, +}; + +export type AssetGateMintArgs = { + /** + * The address of an Asset from the required + * collection that belongs to the payer. + */ + asset: PublicKey; +}; diff --git a/clients/js/src/defaultGuards/default.ts b/clients/js/src/defaultGuards/default.ts index c4ed8dd..ce97c13 100644 --- a/clients/js/src/defaultGuards/default.ts +++ b/clients/js/src/defaultGuards/default.ts @@ -10,6 +10,8 @@ import { AssetBurnArgs, AssetBurnMulti, AssetBurnMultiArgs, + AssetGate, + AssetGateArgs, AssetMintLimit, AssetMintLimitArgs, AssetPayment, @@ -93,6 +95,7 @@ import { AssetBurnMintArgs } from './assetBurn'; import { AssetMintLimitMintArgs } from './assetMintLimit'; import { AssetBurnMultiMintArgs } from './assetBurnMulti'; import { AssetPaymentMultiMintArgs } from './assetPaymentMulti'; +import { AssetGateMintArgs } from './assetGate'; /** * The arguments for all default Candy Machine guards. @@ -127,6 +130,7 @@ export type DefaultGuardSetArgs = GuardSetArgs & { assetMintLimit: OptionOrNullable; assetBurnMulti: OptionOrNullable; assetPaymentMulti: OptionOrNullable; + assetGate: OptionOrNullable; }; /** @@ -162,6 +166,7 @@ export type DefaultGuardSet = GuardSet & { assetMintLimit: Option; assetBurnMulti: Option; assetPaymentMulti: Option; + assetGate: Option; }; /** @@ -197,6 +202,7 @@ export type DefaultGuardSetMintArgs = GuardSetMintArgs & { assetMintLimit: OptionOrNullable; assetBurnMulti: OptionOrNullable; assetPaymentMulti: OptionOrNullable; + assetGate: OptionOrNullable; }; /** @@ -257,6 +263,7 @@ export const defaultCandyGuardNames: string[] = [ 'assetMintLimit', 'assetBurnMulti', 'assetPaymentMulti', + 'assetGate', ]; /** @internal */ diff --git a/clients/js/src/defaultGuards/index.ts b/clients/js/src/defaultGuards/index.ts index a7cde5f..d6abbed 100644 --- a/clients/js/src/defaultGuards/index.ts +++ b/clients/js/src/defaultGuards/index.ts @@ -28,3 +28,4 @@ export * from './assetBurn'; export * from './assetMintLimit'; export * from './assetBurnMulti'; export * from './assetPaymentMulti'; +export * from './assetGate'; \ No newline at end of file diff --git a/clients/js/src/generated/types/assetGate.ts b/clients/js/src/generated/types/assetGate.ts new file mode 100644 index 0000000..934f8a3 --- /dev/null +++ b/clients/js/src/generated/types/assetGate.ts @@ -0,0 +1,32 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; + +/** + * Guard that restricts the transaction to holders of a specified collection. + * + * List of accounts required: + * + * 0. `[]` Account of the Asset. + */ + +export type AssetGate = { requiredCollection: PublicKey }; + +export type AssetGateArgs = AssetGate; + +export function getAssetGateSerializer(): Serializer { + return struct([['requiredCollection', publicKeySerializer()]], { + description: 'AssetGate', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/guardType.ts b/clients/js/src/generated/types/guardType.ts index 3959032..9db9ba1 100644 --- a/clients/js/src/generated/types/guardType.ts +++ b/clients/js/src/generated/types/guardType.ts @@ -39,6 +39,7 @@ export enum GuardType { AssetMintLimit, AssetBurnMulti, AssetPaymentMulti, + AssetGate, } export type GuardTypeArgs = GuardType; diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 8af71ba..3e94d6c 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -11,6 +11,7 @@ export * from './allocation'; export * from './allowList'; export * from './assetBurn'; export * from './assetBurnMulti'; +export * from './assetGate'; export * from './assetMintLimit'; export * from './assetPayment'; export * from './assetPaymentMulti'; diff --git a/clients/js/src/plugin.ts b/clients/js/src/plugin.ts index d3443b6..294f187 100644 --- a/clients/js/src/plugin.ts +++ b/clients/js/src/plugin.ts @@ -31,6 +31,7 @@ import { assetMintLimitGuardManifest, assetBurnMultiGuardManifest, assetPaymentMultiGuardManifest, + assetGateGuardManifest, } from './defaultGuards'; import { createMplCoreCandyGuardProgram, @@ -93,7 +94,8 @@ export const mplCandyMachine = (): UmiPlugin => ({ assetBurnGuardManifest, assetMintLimitGuardManifest, assetBurnMultiGuardManifest, - assetPaymentMultiGuardManifest + assetPaymentMultiGuardManifest, + assetGateGuardManifest, ); }, }); diff --git a/clients/js/test/defaultGuards/assetGate.test.ts b/clients/js/test/defaultGuards/assetGate.test.ts new file mode 100644 index 0000000..923a800 --- /dev/null +++ b/clients/js/test/defaultGuards/assetGate.test.ts @@ -0,0 +1,288 @@ +import { + setComputeUnitLimit, +} from '@metaplex-foundation/mpl-toolbox'; +import { + generateSigner, + sol, + some, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import test from 'ava'; +import { transferV1 } from '@metaplex-foundation/mpl-core'; +import { mintV1 } from '../../src'; +import { + assertBotTax, + assertSuccessfulMint, + createCollection, + createUmi, + createV2, + createAsset, +} from '../_setup'; + +test('it allows minting when the payer owns an asset from a certain collection', async (t) => { + // Given the identity owns an NFT from a certain collection. + const umi = await createUmi(); + const requiredCollectionAuthority = generateSigner(umi); + const { publicKey: requiredCollection } = await createCollection(umi, { + updateAuthority: requiredCollectionAuthority.publicKey, + }); + const nftToVerify = await createAsset(umi, { + owner: umi.identity.publicKey, + collection: requiredCollection, + authority: requiredCollectionAuthority, + }); + + // And a loaded Candy Machine with an assetGate guard. + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + assetGate: some({ requiredCollection }), + }, + }); + + // When we mint from it. + const mint = generateSigner(umi); + await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + mintArgs: { + assetGate: some({ asset: nftToVerify.publicKey }), + }, + }) + ) + .sendAndConfirm(umi); + + // Then minting was successful. + await assertSuccessfulMint(t, umi, { mint, owner: umi.identity }); +}); + +test('it allows minting even when the payer is different from the minter', async (t) => { + // Given a separate minter that owns an NFT from a certain collection. + const umi = await createUmi(); + const minter = generateSigner(umi); + const requiredCollectionAuthority = generateSigner(umi); + const { publicKey: requiredCollection } = await createCollection(umi, { + updateAuthority: requiredCollectionAuthority.publicKey, + }); + const nftToVerify = await createAsset(umi, { + owner: minter.publicKey, + collection: requiredCollection, + authority: requiredCollectionAuthority, + }); + + // And a loaded Candy Machine with an assetGate guard. + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + assetGate: some({ requiredCollection }), + }, + }); + + // When we mint from it. + const mint = generateSigner(umi); + await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + minter, + collection, + mintArgs: { + assetGate: some({ asset: nftToVerify.publicKey }), + }, + }) + ) + .sendAndConfirm(umi); + + // Then minting was successful. + await assertSuccessfulMint(t, umi, { mint, owner: minter }); +}); + +test('it forbids minting when the payer does not own an NFT from a certain collection', async (t) => { + // Given the identity owns an NFT from a certain collection. + const umi = await createUmi(); + const requiredCollectionAuthority = generateSigner(umi); + const { publicKey: requiredCollection } = await createCollection(umi, { + updateAuthority: requiredCollectionAuthority.publicKey, + }); + const { publicKey: nftToVerify } = await createAsset(umi, { + owner: umi.identity.publicKey, + collection: requiredCollection, + authority: requiredCollectionAuthority, + }); + + // But sent their NFT to another wallet. + const destination = generateSigner(umi).publicKey; + await transactionBuilder() + .add( + transferV1(umi, { + authority: umi.identity, + newOwner: destination, + asset: nftToVerify, + collection: requiredCollection, + }) + ) + .sendAndConfirm(umi); + + // And a loaded Candy Machine with an assetGate guard on that collection. + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + assetGate: some({ requiredCollection }), + }, + }); + + // When the payer tries to mint from it. + const mint = generateSigner(umi); + const promise = transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + mintArgs: { + assetGate: some({ asset: nftToVerify }), + }, + }) + ) + .sendAndConfirm(umi); + + // Then we expect an error. + await t.throwsAsync(promise, { message: /MissingNft/ }); +}); + +test('it forbids minting when the payer tries to provide an NFT from the wrong collection', async (t) => { + // Given the identity owns an NFT from a collection A. + const umi = await createUmi(); + const requiredCollectionAuthorityA = generateSigner(umi); + const { publicKey: requiredCollectionA } = await createCollection(umi, { + updateAuthority: requiredCollectionAuthorityA.publicKey, + }); + const { publicKey: nftToVerify } = await createAsset(umi, { + owner: umi.identity.publicKey, + collection: requiredCollectionA, + authority: requiredCollectionAuthorityA, + }); + + // And a loaded Candy Machine with an assetGate guard on a Collection B. + const requiredCollectionAuthorityB = generateSigner(umi); + const { publicKey: requiredCollectionB } = await createCollection(umi, { + updateAuthority: requiredCollectionAuthorityB.publicKey, + }); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + assetGate: some({ requiredCollection: requiredCollectionB }), + }, + }); + + // When the identity tries to mint from it using its collection A NFT. + const mint = generateSigner(umi); + const promise = transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + mintArgs: { + assetGate: some({ asset: nftToVerify }), + }, + }) + ) + .sendAndConfirm(umi); + + // Then we expect an error. + // console.log(await umi.rpc.getTransaction((await promise).signature)); + await t.throwsAsync(promise, { message: /InvalidNftCollection/ }); +}); + +test('it forbids minting when the payer tries to provide an NFT from an unverified collection', async (t) => { + // Given a payer that owns an unverified NFT from a certain collection. + const umi = await createUmi(); + const requiredCollectionAuthority = generateSigner(umi); + const { publicKey: requiredCollection } = await createCollection(umi, { + updateAuthority: requiredCollectionAuthority.publicKey, + }); + const { publicKey: nftToVerify } = await createAsset(umi, { + owner: umi.identity.publicKey, + }); + + // And a loaded Candy Machine with an assetGate guard. + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + assetGate: some({ requiredCollection }), + }, + }); + + // When the payer tries to mint from it using its unverified NFT. + const mint = generateSigner(umi); + const promise = transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + mintArgs: { + assetGate: some({ asset: nftToVerify }), + }, + }) + ) + .sendAndConfirm(umi); + + // Then we expect an error. + await t.throwsAsync(promise, { message: /InvalidNftCollection/ }); +}); + +test('it charges a bot tax when trying to mint without owning the right NFT', async (t) => { + // Given a loaded Candy Machine with an assetGate guard and a bot tax guard. + const umi = await createUmi(); + const { publicKey: requiredCollection } = await createCollection(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + botTax: some({ lamports: sol(0.1), lastInstruction: true }), + assetGate: some({ requiredCollection }), + }, + }); + + // When we try to mint from it using any NFT that's not from the required collection. + const wrongNft = await createAsset(umi, {}); + const mint = generateSigner(umi); + const { signature } = await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + mintArgs: { + assetGate: some({ asset: wrongNft.publicKey }), + }, + }) + ) + .sendAndConfirm(umi); + + // Then we expect a bot tax error. + await assertBotTax(t, umi, mint, signature, /InvalidNftCollection/); +}); diff --git a/idls/candy_guard.json b/idls/candy_guard.json index 37726f3..6920b2c 100644 --- a/idls/candy_guard.json +++ b/idls/candy_guard.json @@ -601,6 +601,25 @@ ] } }, + { + "name": "AssetGate", + "docs": [ + "Guard that restricts the transaction to holders of a specified collection.", + "", + "List of accounts required:", + "", + "0. `[]` Account of the Asset." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "requiredCollection", + "type": "publicKey" + } + ] + } + }, { "name": "AssetMintLimit", "docs": [ @@ -1660,6 +1679,17 @@ "defined": "AssetPaymentMulti" } } + }, + { + "name": "assetGate", + "docs": [ + "Asset Gate (restrict access to holders of a specific asset)." + ], + "type": { + "option": { + "defined": "AssetGate" + } + } } ] } @@ -1775,6 +1805,9 @@ }, { "name": "AssetPaymentMulti" + }, + { + "name": "AssetGate" } ] } diff --git a/programs/candy-guard/program/src/guards/asset_gate.rs b/programs/candy-guard/program/src/guards/asset_gate.rs new file mode 100644 index 0000000..bb0e5c6 --- /dev/null +++ b/programs/candy-guard/program/src/guards/asset_gate.rs @@ -0,0 +1,66 @@ +use mpl_core::accounts::BaseAssetV1; + +use super::*; +use crate::{errors::CandyGuardError, state::GuardType, utils::assert_keys_equal}; + +/// Guard that restricts the transaction to holders of a specified collection. +/// +/// List of accounts required: +/// +/// 0. `[]` Account of the Asset. +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] +pub struct AssetGate { + pub required_collection: Pubkey, +} + +impl Guard for AssetGate { + fn size() -> usize { + 32 // required_collection + } + + fn mask() -> u64 { + GuardType::as_mask(GuardType::AssetGate) + } +} + +impl Condition for AssetGate { + fn validate<'info>( + &self, + ctx: &mut EvaluationContext, + _guard_set: &GuardSet, + _mint_args: &[u8], + ) -> Result<()> { + let index = ctx.account_cursor; + // validates that we received all required accounts + let asset_account = try_get_account_info(ctx.accounts.remaining, index)?; + ctx.account_cursor += 1; + + Self::verify_collection( + asset_account, + &self.required_collection, + ctx.accounts.minter.key, + ) + } +} + +impl AssetGate { + pub fn verify_collection( + asset_account: &AccountInfo, + collection: &Pubkey, + owner: &Pubkey, + ) -> Result<()> { + // validates the metadata information + assert_keys_equal(asset_account.owner, &mpl_core::ID)?; + + let asset: BaseAssetV1 = BaseAssetV1::try_from(asset_account)?; + if asset.update_authority != UpdateAuthority::Collection(*collection) { + return Err(CandyGuardError::InvalidNftCollection.into()); + } + + if asset.owner != *owner { + return Err(CandyGuardError::MissingNft.into()); + } + + Ok(()) + } +} diff --git a/programs/candy-guard/program/src/guards/mod.rs b/programs/candy-guard/program/src/guards/mod.rs index b6a81d0..2986da3 100644 --- a/programs/candy-guard/program/src/guards/mod.rs +++ b/programs/candy-guard/program/src/guards/mod.rs @@ -18,6 +18,7 @@ pub use allocation::Allocation; pub use allow_list::AllowList; pub use asset_burn::AssetBurn; pub use asset_burn_multi::AssetBurnMulti; +pub use asset_gate::AssetGate; pub use asset_mint_limit::AssetMintLimit; pub use asset_payment::AssetPayment; pub use asset_payment_multi::AssetPaymentMulti; @@ -48,6 +49,7 @@ mod allocation; mod allow_list; mod asset_burn; mod asset_burn_multi; +mod asset_gate; mod asset_mint_limit; mod asset_payment; mod asset_payment_multi; diff --git a/programs/candy-guard/program/src/state/candy_guard.rs b/programs/candy-guard/program/src/state/candy_guard.rs index 23ee9f5..38fe5d5 100644 --- a/programs/candy-guard/program/src/state/candy_guard.rs +++ b/programs/candy-guard/program/src/state/candy_guard.rs @@ -140,6 +140,8 @@ pub struct GuardSet { pub asset_burn_multi: Option, /// Asset Payment Multi (multi pay Assets). pub asset_payment_multi: Option, + /// Asset Gate (restrict access to holders of a specific asset). + pub asset_gate: Option, } /// Available guard types. @@ -174,6 +176,7 @@ pub enum GuardType { AssetMintLimit, AssetBurnMulti, AssetPaymentMulti, + AssetGate, } impl GuardType { From 124a9ebc20d7be6e6e7c06168f8215f376a327ec Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Thu, 8 Aug 2024 06:36:08 -0400 Subject: [PATCH 05/31] Fix formatting --- clients/js/src/defaultGuards/assetGate.ts | 4 +--- clients/js/src/defaultGuards/index.ts | 2 +- clients/js/src/plugin.ts | 2 +- clients/js/test/defaultGuards/assetGate.test.ts | 4 +--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/clients/js/src/defaultGuards/assetGate.ts b/clients/js/src/defaultGuards/assetGate.ts index e0878a6..7227dff 100644 --- a/clients/js/src/defaultGuards/assetGate.ts +++ b/clients/js/src/defaultGuards/assetGate.ts @@ -18,9 +18,7 @@ export const assetGateGuardManifest: GuardManifest< serializer: getAssetGateSerializer, mintParser: (context, mintContext, args) => ({ data: new Uint8Array(), - remainingAccounts: [ - { publicKey: args.asset, isWritable: false }, - ], + remainingAccounts: [{ publicKey: args.asset, isWritable: false }], }), routeParser: noopParser, }; diff --git a/clients/js/src/defaultGuards/index.ts b/clients/js/src/defaultGuards/index.ts index d6abbed..12bd9f0 100644 --- a/clients/js/src/defaultGuards/index.ts +++ b/clients/js/src/defaultGuards/index.ts @@ -28,4 +28,4 @@ export * from './assetBurn'; export * from './assetMintLimit'; export * from './assetBurnMulti'; export * from './assetPaymentMulti'; -export * from './assetGate'; \ No newline at end of file +export * from './assetGate'; diff --git a/clients/js/src/plugin.ts b/clients/js/src/plugin.ts index 294f187..a381731 100644 --- a/clients/js/src/plugin.ts +++ b/clients/js/src/plugin.ts @@ -95,7 +95,7 @@ export const mplCandyMachine = (): UmiPlugin => ({ assetMintLimitGuardManifest, assetBurnMultiGuardManifest, assetPaymentMultiGuardManifest, - assetGateGuardManifest, + assetGateGuardManifest ); }, }); diff --git a/clients/js/test/defaultGuards/assetGate.test.ts b/clients/js/test/defaultGuards/assetGate.test.ts index 923a800..2112b0f 100644 --- a/clients/js/test/defaultGuards/assetGate.test.ts +++ b/clients/js/test/defaultGuards/assetGate.test.ts @@ -1,6 +1,4 @@ -import { - setComputeUnitLimit, -} from '@metaplex-foundation/mpl-toolbox'; +import { setComputeUnitLimit } from '@metaplex-foundation/mpl-toolbox'; import { generateSigner, sol, From 41f04bb1d3b38a3f2c48a742ee9148afe70f63ea Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Thu, 8 Aug 2024 12:05:13 -0400 Subject: [PATCH 06/31] Adding a guard for vanity mint addresses. --- clients/js/src/defaultGuards/default.ts | 5 + clients/js/src/defaultGuards/index.ts | 1 + clients/js/src/defaultGuards/vanityMint.ts | 34 +++ clients/js/src/errors.ts | 11 + .../src/generated/errors/mplCoreCandyGuard.ts | 43 ++++ clients/js/src/generated/types/guardType.ts | 1 + clients/js/src/generated/types/index.ts | 1 + clients/js/src/generated/types/vanityMint.ts | 27 +++ clients/js/src/plugin.ts | 4 +- .../js/test/defaultGuards/vanityMint.test.ts | 221 ++++++++++++++++++ idls/candy_guard.json | 44 ++++ programs/candy-guard/Cargo.lock | 23 +- programs/candy-guard/program/Cargo.toml | 27 ++- programs/candy-guard/program/src/errors.rs | 9 + .../candy-guard/program/src/guards/mod.rs | 2 + .../program/src/guards/vanity_mint.rs | 61 +++++ .../program/src/state/candy_guard.rs | 3 + 17 files changed, 497 insertions(+), 20 deletions(-) create mode 100644 clients/js/src/defaultGuards/vanityMint.ts create mode 100644 clients/js/src/generated/types/vanityMint.ts create mode 100644 clients/js/test/defaultGuards/vanityMint.test.ts create mode 100644 programs/candy-guard/program/src/guards/vanity_mint.rs diff --git a/clients/js/src/defaultGuards/default.ts b/clients/js/src/defaultGuards/default.ts index ce97c13..1fd40fe 100644 --- a/clients/js/src/defaultGuards/default.ts +++ b/clients/js/src/defaultGuards/default.ts @@ -60,6 +60,8 @@ import { TokenGateArgs, TokenPayment, TokenPaymentArgs, + VanityMint, + VanityMintArgs, } from '../generated'; import { GuardSet, @@ -131,6 +133,7 @@ export type DefaultGuardSetArgs = GuardSetArgs & { assetBurnMulti: OptionOrNullable; assetPaymentMulti: OptionOrNullable; assetGate: OptionOrNullable; + vanityMint: OptionOrNullable; }; /** @@ -167,6 +170,7 @@ export type DefaultGuardSet = GuardSet & { assetBurnMulti: Option; assetPaymentMulti: Option; assetGate: Option; + vanityMint: Option; }; /** @@ -264,6 +268,7 @@ export const defaultCandyGuardNames: string[] = [ 'assetBurnMulti', 'assetPaymentMulti', 'assetGate', + 'vanityMint', ]; /** @internal */ diff --git a/clients/js/src/defaultGuards/index.ts b/clients/js/src/defaultGuards/index.ts index 12bd9f0..a6f5fee 100644 --- a/clients/js/src/defaultGuards/index.ts +++ b/clients/js/src/defaultGuards/index.ts @@ -29,3 +29,4 @@ export * from './assetMintLimit'; export * from './assetBurnMulti'; export * from './assetPaymentMulti'; export * from './assetGate'; +export * from './vanityMint'; diff --git a/clients/js/src/defaultGuards/vanityMint.ts b/clients/js/src/defaultGuards/vanityMint.ts new file mode 100644 index 0000000..a4edf26 --- /dev/null +++ b/clients/js/src/defaultGuards/vanityMint.ts @@ -0,0 +1,34 @@ +import { + fixSerializer, + mapSerializer, +} from '@metaplex-foundation/umi/serializers'; +import { ExceededRegexLengthError } from '../errors'; +import { + getVanityMintSerializer, + VanityMint, + VanityMintArgs, +} from '../generated'; +import { GuardManifest, noopParser } from '../guards'; + +/** + * The vanityMint guard determines the start date of the mint. + * Before this date, minting is not allowed. + */ +export const vanityMintGuardManifest: GuardManifest< + VanityMintArgs, + VanityMint +> = { + name: 'vanityMint', + serializer: () => + mapSerializer( + fixSerializer(getVanityMintSerializer(), 4 + 100), + (value) => { + if (value.regex.length > 100) { + throw new ExceededRegexLengthError(); + } + return value; + } + ), + mintParser: noopParser, + routeParser: noopParser, +}; diff --git a/clients/js/src/errors.ts b/clients/js/src/errors.ts index 76f9bdb..beeb970 100644 --- a/clients/js/src/errors.ts +++ b/clients/js/src/errors.ts @@ -161,3 +161,14 @@ export class MaximumOfFiveAdditionalProgramsError extends CandyMachineError { super(message); } } + +export class ExceededRegexLengthError extends CandyMachineError { + readonly name: string = 'ExceededRegexLengthError'; + + constructor() { + const message = + `The maximum length of a regex that can be used is 100 characters. ` + + 'Please reduce the length of the regex to <= 100.'; + super(message); + } +} diff --git a/clients/js/src/generated/errors/mplCoreCandyGuard.ts b/clients/js/src/generated/errors/mplCoreCandyGuard.ts index eb6e127..58268aa 100644 --- a/clients/js/src/generated/errors/mplCoreCandyGuard.ts +++ b/clients/js/src/generated/errors/mplCoreCandyGuard.ts @@ -731,6 +731,49 @@ export class CgInvalidAccountVersionError extends ProgramError { codeToErrorMap.set(0x17a3, CgInvalidAccountVersionError); nameToErrorMap.set('InvalidAccountVersion', CgInvalidAccountVersionError); +/** ExceededRegexLength: Exceeded the maximum length of a regex that can be used */ +export class CgExceededRegexLengthError extends ProgramError { + readonly name: string = 'ExceededRegexLength'; + + readonly code: number = 0x17a4; // 6052 + + constructor(program: Program, cause?: Error) { + super( + 'Exceeded the maximum length of a regex that can be used', + program, + cause + ); + } +} +codeToErrorMap.set(0x17a4, CgExceededRegexLengthError); +nameToErrorMap.set('ExceededRegexLength', CgExceededRegexLengthError); + +/** InvalidVanityAddress: Invalid vanity address */ +export class CgInvalidVanityAddressError extends ProgramError { + readonly name: string = 'InvalidVanityAddress'; + + readonly code: number = 0x17a5; // 6053 + + constructor(program: Program, cause?: Error) { + super('Invalid vanity address', program, cause); + } +} +codeToErrorMap.set(0x17a5, CgInvalidVanityAddressError); +nameToErrorMap.set('InvalidVanityAddress', CgInvalidVanityAddressError); + +/** InvalidRegex: Invalid regex */ +export class CgInvalidRegexError extends ProgramError { + readonly name: string = 'InvalidRegex'; + + readonly code: number = 0x17a6; // 6054 + + constructor(program: Program, cause?: Error) { + super('Invalid regex', program, cause); + } +} +codeToErrorMap.set(0x17a6, CgInvalidRegexError); +nameToErrorMap.set('InvalidRegex', CgInvalidRegexError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/types/guardType.ts b/clients/js/src/generated/types/guardType.ts index 9db9ba1..3db7d15 100644 --- a/clients/js/src/generated/types/guardType.ts +++ b/clients/js/src/generated/types/guardType.ts @@ -40,6 +40,7 @@ export enum GuardType { AssetBurnMulti, AssetPaymentMulti, AssetGate, + VanityMint, } export type GuardTypeArgs = GuardType; diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 3e94d6c..5bd9232 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -42,3 +42,4 @@ export * from './token2022Payment'; export * from './tokenBurn'; export * from './tokenGate'; export * from './tokenPayment'; +export * from './vanityMint'; diff --git a/clients/js/src/generated/types/vanityMint.ts b/clients/js/src/generated/types/vanityMint.ts new file mode 100644 index 0000000..441beae --- /dev/null +++ b/clients/js/src/generated/types/vanityMint.ts @@ -0,0 +1,27 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Serializer, + string, + struct, +} from '@metaplex-foundation/umi/serializers'; + +/** Guard that sets a specific start date for the mint. */ +export type VanityMint = { regex: string }; + +export type VanityMintArgs = VanityMint; + +export function getVanityMintSerializer(): Serializer< + VanityMintArgs, + VanityMint +> { + return struct([['regex', string()]], { + description: 'VanityMint', + }) as Serializer; +} diff --git a/clients/js/src/plugin.ts b/clients/js/src/plugin.ts index a381731..783a335 100644 --- a/clients/js/src/plugin.ts +++ b/clients/js/src/plugin.ts @@ -32,6 +32,7 @@ import { assetBurnMultiGuardManifest, assetPaymentMultiGuardManifest, assetGateGuardManifest, + vanityMintGuardManifest, } from './defaultGuards'; import { createMplCoreCandyGuardProgram, @@ -95,7 +96,8 @@ export const mplCandyMachine = (): UmiPlugin => ({ assetMintLimitGuardManifest, assetBurnMultiGuardManifest, assetPaymentMultiGuardManifest, - assetGateGuardManifest + assetGateGuardManifest, + vanityMintGuardManifest ); }, }); diff --git a/clients/js/test/defaultGuards/vanityMint.test.ts b/clients/js/test/defaultGuards/vanityMint.test.ts new file mode 100644 index 0000000..e726640 --- /dev/null +++ b/clients/js/test/defaultGuards/vanityMint.test.ts @@ -0,0 +1,221 @@ +import { setComputeUnitLimit } from '@metaplex-foundation/mpl-toolbox'; +import { + generateSigner, + sol, + some, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import test from 'ava'; +import { mintV1 } from '../../src'; +import { + assertBotTax, + assertSuccessfulMint, + createCollection, + createUmi, + createV2, +} from '../_setup'; + +test('it can mint an asset that starts with a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `^${mint.publicKey.toString().slice(0, 5)}` }), + }, + }); + + // When we mint from it. + await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was successful. + await assertSuccessfulMint(t, umi, { mint, owner: umi.identity }); +}); + +test('it cannot mint an asset that does not start with a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `^OOOO` }), + }, + }); + + // When we mint from it. + const promise = transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was unsuccessful. + await t.throwsAsync(promise, { message: /InvalidVanityAddress/ }); +}); + +test('it can mint an asset that ends with a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `${mint.publicKey.toString().slice(-4)}$` }), + }, + }); + + // When we mint from it. + await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was successful. + await assertSuccessfulMint(t, umi, { mint, owner: umi.identity }); +}); + +test('it cannot mint an asset that does not end with a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `OOOO$` }), + }, + }); + + // When we mint from it. + const promise = transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was unsuccessful. + await t.throwsAsync(promise, { message: /InvalidVanityAddress/ }); +}); + +test('it can mint an asset that exactly matches a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `^${mint.publicKey.toString()}$` }), + }, + }); + + // When we mint from it. + await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was successful. + await assertSuccessfulMint(t, umi, { mint, owner: umi.identity }); +}); + +test('it cannot mint an asset that does not exactly match a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `^${mint.publicKey.toString().slice(1)}$` }), + }, + }); + + // When we mint from it. + const promise = transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was unsuccessful. + await t.throwsAsync(promise, { message: /InvalidVanityAddress/ }); +}); + +test('it charges a bot tax when trying to mint an asset that starts with a specific pattern', async (t) => { + // Given a candy machine with a bot tax and start date in the future. + const umi = await createUmi(); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + botTax: some({ lamports: sol(0.01), lastInstruction: true }), + // O is not valid in bs58, so this will always fail. + vanityMint: some({ regex: `^OOOO` }), + }, + }); + + // When we mint from it. + const mint = generateSigner(umi); + const { signature } = await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then we expect a silent bot tax error. + await assertBotTax(t, umi, mint, signature, /InvalidVanityAddress/); +}); diff --git a/idls/candy_guard.json b/idls/candy_guard.json index 6920b2c..fab6b37 100644 --- a/idls/candy_guard.json +++ b/idls/candy_guard.json @@ -1280,6 +1280,21 @@ ] } }, + { + "name": "VanityMint", + "docs": [ + "Guard that sets a specific start date for the mint." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "regex", + "type": "string" + } + ] + } + }, { "name": "RouteArgs", "docs": [ @@ -1690,6 +1705,17 @@ "defined": "AssetGate" } } + }, + { + "name": "vanityMint", + "docs": [ + "Vanity Mint (the address of the new asset must match a pattern)." + ], + "type": { + "option": { + "defined": "VanityMint" + } + } } ] } @@ -1808,6 +1834,9 @@ }, { "name": "AssetGate" + }, + { + "name": "VanityMint" } ] } @@ -2073,6 +2102,21 @@ "code": 6051, "name": "InvalidAccountVersion", "msg": "Invalid account version" + }, + { + "code": 6052, + "name": "ExceededRegexLength", + "msg": "Exceeded the maximum length of a regex that can be used" + }, + { + "code": 6053, + "name": "InvalidVanityAddress", + "msg": "Invalid vanity address" + }, + { + "code": 6054, + "name": "InvalidRegex", + "msg": "Invalid regex" } ], "metadata": { diff --git a/programs/candy-guard/Cargo.lock b/programs/candy-guard/Cargo.lock index 06dc934..bf578cf 100644 --- a/programs/candy-guard/Cargo.lock +++ b/programs/candy-guard/Cargo.lock @@ -1197,9 +1197,9 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -1255,6 +1255,7 @@ dependencies = [ "mpl-core-candy-guard-derive", "mpl-core-candy-machine-core", "mpl-token-metadata", + "regex-lite", "solana-gateway", "solana-program", "spl-associated-token-account", @@ -1631,9 +1632,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -1643,20 +1644,26 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustc-hash" diff --git a/programs/candy-guard/program/Cargo.toml b/programs/candy-guard/program/Cargo.toml index a628d05..a960256 100644 --- a/programs/candy-guard/program/Cargo.toml +++ b/programs/candy-guard/program/Cargo.toml @@ -1,31 +1,36 @@ [package] -name = "mpl-core-candy-guard" -version = "0.2.1" -description = "Metaplex Candy Guard: programmatic access control for Candy Machine." authors = ["Metaplex Developers "] -repository = "https://github.com/metaplex-foundation/mpl-candy-machine-asset" -license-file = "../../../LICENSE" +description = "Metaplex Candy Guard: programmatic access control for Candy Machine." edition = "2021" +license-file = "../../../LICENSE" +name = "mpl-core-candy-guard" readme = "../README.md" +repository = "https://github.com/metaplex-foundation/mpl-candy-machine-asset" +version = "0.2.1" [lib] crate-type = ["cdylib", "lib"] [features] -no-entrypoint = [] -test-bpf = [] cpi = ["no-entrypoint"] default = [] +no-entrypoint = [] +test-bpf = [] [dependencies] anchor-lang = "0.28.0" arrayref = "0.3.6" +mpl-core = { version = "0.6.1" } mpl-core-candy-guard-derive = { path = "../macro", version = "0.2.1" } -mpl-core-candy-machine-core = { path = "../../candy-machine-core/program", version = "0.2.0", features = ["cpi"] } +mpl-core-candy-machine-core = { path = "../../candy-machine-core/program", version = "0.2.0", features = [ + "cpi", +] } mpl-token-metadata = "3.2.1" -mpl-core = { version = "0.6.1" } +regex-lite = "0.1.6" +solana-gateway = { version = "0.4.0", features = ["no-entrypoint"] } solana-program = "~1.16.5" -spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } +spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = [ + "no-entrypoint", +] } spl-token = { version = ">= 3.5.0, < 5.0", features = ["no-entrypoint"] } spl-token-2022 = { version = ">= 0.6", features = ["no-entrypoint"] } -solana-gateway = { version = "0.4.0", features = ["no-entrypoint"] } diff --git a/programs/candy-guard/program/src/errors.rs b/programs/candy-guard/program/src/errors.rs index 8311452..b80b539 100644 --- a/programs/candy-guard/program/src/errors.rs +++ b/programs/candy-guard/program/src/errors.rs @@ -157,4 +157,13 @@ pub enum CandyGuardError { #[msg("Invalid account version")] InvalidAccountVersion, + + #[msg("Exceeded the maximum length of a regex that can be used")] + ExceededRegexLength, + + #[msg("Invalid vanity address")] + InvalidVanityAddress, + + #[msg("Invalid regex")] + InvalidRegex, } diff --git a/programs/candy-guard/program/src/guards/mod.rs b/programs/candy-guard/program/src/guards/mod.rs index 2986da3..1113fe3 100644 --- a/programs/candy-guard/program/src/guards/mod.rs +++ b/programs/candy-guard/program/src/guards/mod.rs @@ -43,6 +43,7 @@ pub use token2022_payment::Token2022Payment; pub use token_burn::TokenBurn; pub use token_gate::TokenGate; pub use token_payment::TokenPayment; +pub use vanity_mint::VanityMint; mod address_gate; mod allocation; @@ -74,6 +75,7 @@ mod token2022_payment; mod token_burn; mod token_gate; mod token_payment; +mod vanity_mint; pub trait Condition { /// Validate the condition of the guard. When the guard condition is diff --git a/programs/candy-guard/program/src/guards/vanity_mint.rs b/programs/candy-guard/program/src/guards/vanity_mint.rs new file mode 100644 index 0000000..c816626 --- /dev/null +++ b/programs/candy-guard/program/src/guards/vanity_mint.rs @@ -0,0 +1,61 @@ +use regex_lite::Regex; + +use crate::state::GuardType; + +use super::*; + +const MAXIMUM_LENGTH: usize = 100; + +/// Guard that sets a specific start date for the mint. +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] +pub struct VanityMint { + pub regex: String, +} + +impl Guard for VanityMint { + fn size() -> usize { + 4 + // String prefix + 100 // MAXIMUM_LENGTH + } + + fn mask() -> u64 { + GuardType::as_mask(GuardType::VanityMint) + } + + fn verify(data: &CandyGuardData) -> Result<()> { + if let Some(vanity_mint) = &data.default.vanity_mint { + if vanity_mint.regex.len() > MAXIMUM_LENGTH { + return err!(CandyGuardError::ExceededRegexLength); + } + } + + if let Some(groups) = &data.groups { + for group in groups { + if let Some(vanity_mint) = &group.guards.vanity_mint { + if vanity_mint.regex.len() > MAXIMUM_LENGTH { + return err!(CandyGuardError::ExceededRegexLength); + } + } + } + } + + Ok(()) + } +} + +impl Condition for VanityMint { + fn validate<'info>( + &self, + ctx: &mut EvaluationContext, + _guard_set: &GuardSet, + _mint_args: &[u8], + ) -> Result<()> { + let mint_address = ctx.accounts.asset.key().to_string(); + let regex = Regex::new(&self.regex).map_err(|_| CandyGuardError::InvalidRegex)?; + if !regex.is_match(&mint_address) { + return err!(CandyGuardError::InvalidVanityAddress); + } + + Ok(()) + } +} diff --git a/programs/candy-guard/program/src/state/candy_guard.rs b/programs/candy-guard/program/src/state/candy_guard.rs index 38fe5d5..f7c650d 100644 --- a/programs/candy-guard/program/src/state/candy_guard.rs +++ b/programs/candy-guard/program/src/state/candy_guard.rs @@ -142,6 +142,8 @@ pub struct GuardSet { pub asset_payment_multi: Option, /// Asset Gate (restrict access to holders of a specific asset). pub asset_gate: Option, + /// Vanity Mint (the address of the new asset must match a pattern). + pub vanity_mint: Option, } /// Available guard types. @@ -177,6 +179,7 @@ pub enum GuardType { AssetBurnMulti, AssetPaymentMulti, AssetGate, + VanityMint, } impl GuardType { From 8c70aac090d90331300634b4f5c09c151aae9872 Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Thu, 8 Aug 2024 12:15:45 -0400 Subject: [PATCH 07/31] Fixing comment. --- clients/js/src/defaultGuards/vanityMint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/js/src/defaultGuards/vanityMint.ts b/clients/js/src/defaultGuards/vanityMint.ts index a4edf26..8be9df7 100644 --- a/clients/js/src/defaultGuards/vanityMint.ts +++ b/clients/js/src/defaultGuards/vanityMint.ts @@ -11,8 +11,8 @@ import { import { GuardManifest, noopParser } from '../guards'; /** - * The vanityMint guard determines the start date of the mint. - * Before this date, minting is not allowed. + * The vanityMint guard verifies that the new asset + * address matches the provided regex. */ export const vanityMintGuardManifest: GuardManifest< VanityMintArgs, From f4963ca565399b3cb2a27639138b1c17daf4cb68 Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Thu, 8 Aug 2024 13:53:08 -0400 Subject: [PATCH 08/31] Bump solana version. --- .github/.env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/.env b/.github/.env index c02d90b..ddafd7c 100644 --- a/.github/.env +++ b/.github/.env @@ -2,8 +2,8 @@ CARGO_TERM_COLOR=always NODE_VERSION=16.x PROGRAMS=["candy-machine-core", "candy-guard"] RUST_VERSION=1.75.0 -SOLANA_VERSION=1.17.20 +SOLANA_VERSION=1.18.21 ANCHOR_VERSION=0.27.0 COMMIT_USER_NAME="github-actions[bot]" COMMIT_USER_EMAIL="41898282+github-actions[bot]@users.noreply.github.com" -DEPLOY_SOLANA_VERSION=1.18.14 +DEPLOY_SOLANA_VERSION=1.18.21 From 7fb0640414b615c0b1d0bffd4822145c111d4753 Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Fri, 16 Aug 2024 08:47:03 -0400 Subject: [PATCH 09/31] Bumping anchor-cli version. --- .github/.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/.env b/.github/.env index ddafd7c..5bb9dca 100644 --- a/.github/.env +++ b/.github/.env @@ -3,7 +3,7 @@ NODE_VERSION=16.x PROGRAMS=["candy-machine-core", "candy-guard"] RUST_VERSION=1.75.0 SOLANA_VERSION=1.18.21 -ANCHOR_VERSION=0.27.0 +ANCHOR_VERSION=0.29.0 COMMIT_USER_NAME="github-actions[bot]" COMMIT_USER_EMAIL="41898282+github-actions[bot]@users.noreply.github.com" DEPLOY_SOLANA_VERSION=1.18.21 From ea889a3b406ed7278f5b56dbf59aeb38d24144bc Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Fri, 16 Aug 2024 13:13:17 +0000 Subject: [PATCH 10/31] Deploy JS client v0.2.3 --- clients/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/js/package.json b/clients/js/package.json index 79d6d7f..0afa849 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -1,6 +1,6 @@ { "name": "@metaplex-foundation/mpl-core-candy-machine", - "version": "0.2.2", + "version": "0.2.3", "description": "Client library for Candy Machine related programs", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", From aa4c0e45ad12b01a23e69b478f7c7004e9427015 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:35:00 -0700 Subject: [PATCH 11/31] Upload .bin directory after github change --- .github/workflows/build-programs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-programs.yml b/.github/workflows/build-programs.yml index 82850f0..b81f505 100644 --- a/.github/workflows/build-programs.yml +++ b/.github/workflows/build-programs.yml @@ -62,4 +62,5 @@ jobs: name: program-builds # First wildcard ensures exported paths are consistently under the programs folder. path: ./program*/.bin/*.so + include-hidden-files: true if-no-files-found: error From c4d96f5a4deeb8e1244b3bf7a9f9cc64e3a879e0 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Wed, 11 Sep 2024 22:04:07 -0700 Subject: [PATCH 12/31] multi chain release flow (#18) * add create release flow, remove anchor idl publish * add start validator artifact name --- .github/workflows/build-programs.yml | 8 +- .github/workflows/create-release.yml | 116 ++++++++++++++++++++++++++ .github/workflows/deploy-program.yml | 120 ++++++++++++--------------- .github/workflows/main.yml | 2 +- .github/workflows/test-js.yml | 8 +- .github/workflows/test-programs.yml | 6 +- 6 files changed, 187 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/create-release.yml diff --git a/.github/workflows/build-programs.yml b/.github/workflows/build-programs.yml index b81f505..00a452c 100644 --- a/.github/workflows/build-programs.yml +++ b/.github/workflows/build-programs.yml @@ -7,6 +7,8 @@ on: type: string solana: type: string + git_ref: + type: string workflow_dispatch: inputs: rust: @@ -29,7 +31,9 @@ jobs: runs-on: ubuntu-latest-16-cores steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV @@ -59,7 +63,7 @@ jobs: - name: Upload program builds uses: actions/upload-artifact@v4 with: - name: program-builds + name: program-builds-${{ inputs.git_ref }} # First wildcard ensures exported paths are consistently under the programs folder. path: ./program*/.bin/*.so include-hidden-files: true diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..8e8ca6d --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,116 @@ +name: Tag release + +on: + workflow_dispatch: + inputs: + program: + description: Program + required: true + default: candy-guard + type: choice + options: + - candy-guard + - candy-machine-core + bump: + description: Version bump + required: true + default: patch + type: choice + options: + - patch + - minor + - major + git_ref: + description: Commit hash or branch to create release + required: false + type: string + default: main + + +env: + CACHE: true + +jobs: + build_programs: + name: Programs + uses: ./.github/workflows/build-programs.yml + secrets: inherit + with: + git_ref: ${{ inputs.git_ref }} + + test_js: + name: JS client + needs: build_programs + uses: ./.github/workflows/test-js.yml + secrets: inherit + with: + git_ref: ${{ inputs.git_ref }} + + create_release: + name: Create program release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Git checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} + - name: Bump Program Version + run: | + git fetch --tags --all + VERSION=`git tag -l --sort -version:refname "release/${{ inputs.program }}/*" | head -n 1 | sed 's|release/||'` + MAJOR=`echo ${VERSION} | cut -d. -f1` + MINOR=`echo ${VERSION} | cut -d. -f2` + PATCH=`echo ${VERSION} | cut -d. -f3` + + if [ "${{ inputs.bump }}" == "major" ]; then + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + elif [ "${{ inputs.bump }}" == "minor" ]; then + MINOR=$((MINOR + 1)) + PATCH=0 + else + PATCH=$((PATCH + 1)) + fi + + PROGRAM_VERSION="${MAJOR}.${MINOR}.${PATCH}" + + echo PROGRAM_VERSION="${PROGRAM_VERSION}" >> $GITHUB_ENV + + - name: Download Program Builds + uses: actions/download-artifact@v4 + with: + name: program-builds-${{ inputs.git_ref }} + + - name: Identify Program + run: | + if [ "${{ inputs.program }}" == "candy-guard" ]; then + echo PROGRAM_NAME="mpl_core_candy_guard" >> $GITHUB_ENV + else + echo PROGRAM_NAME="mpl_core_candy_machine_core" >> $GITHUB_ENV + fi + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: release/${{ inputs.program }}/${{ env.PROGRAM_VERSION }} + release_name: Release ${{ inputs.program }} v${{ env.PROGRAM_VERSION }} + body: | + Release ${{ inputs.program }} v${{ env.PROGRAM_VERSION }} + draft: false + prerelease: false + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./programs/.bin/${{ env.PROGRAM_NAME }}.so + asset_name: ${{ inputs.program }}.so + asset_content_type: application/octet-stream \ No newline at end of file diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 1e5a7df..70494ae 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -19,20 +19,20 @@ on: options: - devnet - mainnet-beta - publish_crate: - description: Release cargo crate + - sonic-devnet + - sonic-testnet + - eclipse-mainnet + - eclipse-devnet + git_ref: + description: Release tag (release/1.x.x) or commit to deploy required: true + type: string + default: release/1.0.0 + dry_run: + description: Dry run + required: false type: boolean default: false - bump: - description: Version bump - required: true - default: patch - type: choice - options: - - patch - - minor - - major env: CACHE: true @@ -42,12 +42,16 @@ jobs: name: Programs uses: ./.github/workflows/build-programs.yml secrets: inherit + with: + git_ref: ${{ inputs.git_ref }} test_js: name: JS client needs: build_programs uses: ./.github/workflows/test-js.yml secrets: inherit + with: + git_ref: ${{ inputs.git_ref }} deploy_program: name: Program / Deploy @@ -57,7 +61,9 @@ jobs: contents: write steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV @@ -73,12 +79,6 @@ jobs: version: ${{ env.DEPLOY_SOLANA_VERSION }} cache: ${{ env.CACHE }} - - name: Install Anchor CLI - uses: metaplex-foundation/actions/install-anchor-cli@v1 - with: - version: ${{ env.ANCHOR_VERSION }} - cache: ${{ env.CACHE }} - - name: Install cargo-release uses: metaplex-foundation/actions/install-cargo-release@v1 if: github.event.inputs.publish_crate == 'true' @@ -87,57 +87,51 @@ jobs: - name: Set RPC run: | + # We do this if waterfall because github actions does not allow dynamic access to secrets if [ "${{ inputs.cluster }}" == "devnet" ]; then echo RPC=${{ secrets.DEVNET_RPC }} >> $GITHUB_ENV - else + else if [ "${{ inputs.cluster }}" == "mainnet-beta" ]; then echo RPC=${{ secrets.MAINNET_RPC }} >> $GITHUB_ENV + else if [ "${{ inputs.cluster }}" == "sonic-devnet" ]; then + echo RPC=${{ secrets.SONIC_DEVNET_RPC }} >> $GITHUB_ENV + else if [ "${{ inputs.cluster }}" == "sonic-testnet" ]; then + echo RPC=${{ secrets.SONIC_TESTNET_RPC }} >> $GITHUB_ENV + else if [ "${{ inputs.cluster }}" == "eclipse-devnet" ]; then + echo RPC=${{ secrets.ECLIPSE_DEVNET_RPC }} >> $GITHUB_ENV + else if [ "${{ inputs.cluster }}" == "eclipse-testnet" ]; then + echo RPC=${{ secrets.ECLIPSE_TESTNET_RPC }} >> $GITHUB_ENV + else if [ "${{ inputs.cluster }}" == "eclipse-mainnet" ]; then + echo RPC=${{ secrets.ECLIPSE_MAINNET_RPC }} >> $GITHUB_ENV fi - name: Identify Program run: | + if [[ "${{ inputs.cluster }}" == "sonic"* ]]; then + echo ${{ secrets.CORE_CANDY_MACHINE_SONIC_DEPLOY_KEY }} > ./deploy-key.json + else if [[ "${{ inputs.cluster }}" == "eclipse"* ]]; then + echo ${{ secrets.CORE_CANDY_MACHINE_ECLIPSE_DEPLOY_KEY }} > ./deploy-key.json + fi if [ "${{ inputs.program }}" == "candy-guard" ]; then - echo ${{ secrets.CORE_CANDY_GUARD_DEPLOY_KEY }} > ./deploy-key.json + if [ ! -e "./deploy-key.json" ]; then + echo ${{ secrets.CORE_CANDY_GUARD_DEPLOY_KEY }} > ./deploy-key.json + fi echo ${{ secrets.CORE_CANDY_GUARD_ID }} > ./program-id.json echo PROGRAM_NAME="mpl_core_candy_guard" >> $GITHUB_ENV else - echo ${{ secrets.CORE_CANDY_MACHINE_CORE_DEPLOY_KEY }} > ./deploy-key.json + if [ ! -e "./deploy-key.json" ]; then + echo ${{ secrets.CORE_CANDY_MACHINE_CORE_DEPLOY_KEY }} > ./deploy-key.json + fi echo ${{ secrets.CORE_CANDY_MACHINE_CORE_ID }} > ./program-id.json echo PROGRAM_NAME="mpl_core_candy_machine_core" >> $GITHUB_ENV fi - - name: Bump Program Version - run: | - IDL_NAME=`echo "${{ inputs.program }}" | tr - _` - VERSION=`jq '.version' ./idls/${IDL_NAME}.json | sed 's/"//g'` - MAJOR=`echo ${VERSION} | cut -d. -f1` - MINOR=`echo ${VERSION} | cut -d. -f2` - PATCH=`echo ${VERSION} | cut -d. -f3` - - if [ "${{ inputs.bump }}" == "major" ]; then - MAJOR=$((MAJOR + 1)) - MINOR=0 - PATCH=0 - elif [ "${{ inputs.bump }}" == "minor" ]; then - MINOR=$((MINOR + 1)) - PATCH=0 - else - PATCH=$((PATCH + 1)) - fi - - PROGRAM_VERSION="${MAJOR}.${MINOR}.${PATCH}" - - cp ./idls/${IDL_NAME}.json ./idls/${IDL_NAME}-previous.json - jq ".version = \"${PROGRAM_VERSION}\"" ./idls/${IDL_NAME}-previous.json > ./idls/${IDL_NAME}.json - rm ./idls/${IDL_NAME}-previous.json - - echo PROGRAM_VERSION="${PROGRAM_VERSION}" >> $GITHUB_ENV - - name: Download Program Builds uses: actions/download-artifact@v4 with: - name: program-builds + name: program-builds-${{ inputs.git_ref }} - name: Deploy Program + if: !github.event.inputs.dry_run run: | echo "Deploying ${{ inputs.program }} to ${{ inputs.cluster }}" @@ -148,20 +142,6 @@ jobs: --max-sign-attempts 100 \ --use-rpc - - name: Upgrade IDL - working-directory: ./programs/${{ inputs.program }} - run: | - jq 'del(.metadata?) | del(.. | .docs?)' ../../idls/`echo "${{ inputs.program }}" | tr - _`.json > ./idl.json - - anchor idl upgrade -f ./idl.json \ - --provider.cluster ${{ env.RPC }} \ - --provider.wallet ../../deploy-key.json \ - `solana address -k ../../program-id.json` - - rm ../../deploy-key.json - rm ../../program-id.json - rm ./idl.json - - name: Publish crate working-directory: ./programs/${{ inputs.program }}/program if: github.event.inputs.publish_crate == 'true' && github.event.inputs.cluster == 'mainnet-beta' @@ -176,9 +156,13 @@ jobs: git reset --soft HEAD~1 git stash pop - - name: Commit and tag new version - uses: stefanzweifel/git-auto-commit-action@v4 - if: github.event.inputs.publish_crate == 'true' && github.event.inputs.cluster == 'mainnet-beta' + - name: Create tag + uses: actions/github-script@v5 with: - commit_message: Deploy mpl-${{ inputs.program }} program v${{ env.PROGRAM_VERSION }} - tagging_message: mpl-${{ inputs.program }}@v${{ env.PROGRAM_VERSION }} + script: | + github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'refs/tags/${{ inputs.program }}-${{ inputs.cluster }}', + sha: '${{ inputs.git_ref }}' + }); diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 61078c8..bf94c3e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -66,7 +66,7 @@ jobs: pull-requests: write steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 5b0f891..042b5bb 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -2,6 +2,9 @@ name: Test JS client on: workflow_call: + inputs: + git_ref: + type: string env: CACHE: true @@ -15,7 +18,9 @@ jobs: node: ["16.x", "18.x"] steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV @@ -26,6 +31,7 @@ jobs: node: ${{ matrix.node }} solana: ${{ env.SOLANA_VERSION }} cache: ${{ env.CACHE }} + artifacts: program-builds-${{ inputs.git_ref }} - name: Install dependencies uses: metaplex-foundation/actions/install-node-dependencies@v1 diff --git a/.github/workflows/test-programs.yml b/.github/workflows/test-programs.yml index 1098f7c..80f7d75 100644 --- a/.github/workflows/test-programs.yml +++ b/.github/workflows/test-programs.yml @@ -5,6 +5,8 @@ on: inputs: program_matrix: type: string + git_ref: + type: string env: CACHE: true @@ -18,7 +20,9 @@ jobs: program: ${{ fromJson(inputs.program_matrix) }} steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV From 848d6504033a23972b5095655b287c53cce0b920 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Wed, 11 Sep 2024 22:18:24 -0700 Subject: [PATCH 13/31] minor versioning fix --- .github/workflows/create-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 8e8ca6d..816273f 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -1,4 +1,4 @@ -name: Tag release +name: Create release on: workflow_dispatch: @@ -59,7 +59,7 @@ jobs: - name: Bump Program Version run: | git fetch --tags --all - VERSION=`git tag -l --sort -version:refname "release/${{ inputs.program }}/*" | head -n 1 | sed 's|release/||'` + VERSION=`git tag -l --sort -version:refname "release/${{ inputs.program }}/*" | head -n 1 | sed 's|release/${{ inputs.program }}/||'` MAJOR=`echo ${VERSION} | cut -d. -f1` MINOR=`echo ${VERSION} | cut -d. -f2` PATCH=`echo ${VERSION} | cut -d. -f3` From d29dad878672a0d3bd4b32a9883f77e072bdc36b Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Wed, 11 Sep 2024 22:26:11 -0700 Subject: [PATCH 14/31] add release dep --- .github/workflows/create-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 816273f..051a292 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -49,6 +49,7 @@ jobs: create_release: name: Create program release runs-on: ubuntu-latest + needs: test_js permissions: contents: write steps: From 8d262834973a061a39c1ce68f66e041b4cc96f35 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Wed, 11 Sep 2024 22:41:17 -0700 Subject: [PATCH 15/31] nicer tag formatting --- .github/workflows/create-release.yml | 6 +++--- .github/workflows/deploy-program.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 051a292..b2b7768 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -60,7 +60,7 @@ jobs: - name: Bump Program Version run: | git fetch --tags --all - VERSION=`git tag -l --sort -version:refname "release/${{ inputs.program }}/*" | head -n 1 | sed 's|release/${{ inputs.program }}/||'` + VERSION=`git tag -l --sort -version:refname "release/${{ inputs.program }}@*" | head -n 1 | sed 's|release/${{ inputs.program }}@||'` MAJOR=`echo ${VERSION} | cut -d. -f1` MINOR=`echo ${VERSION} | cut -d. -f2` PATCH=`echo ${VERSION} | cut -d. -f3` @@ -99,8 +99,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: release/${{ inputs.program }}/${{ env.PROGRAM_VERSION }} - release_name: Release ${{ inputs.program }} v${{ env.PROGRAM_VERSION }} + tag_name: release/${{ inputs.program }}@${{ env.PROGRAM_VERSION }} + release_name: ${{ inputs.program }} v${{ env.PROGRAM_VERSION }} body: | Release ${{ inputs.program }} v${{ env.PROGRAM_VERSION }} draft: false diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 70494ae..bd7cbf8 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -24,10 +24,10 @@ on: - eclipse-mainnet - eclipse-devnet git_ref: - description: Release tag (release/1.x.x) or commit to deploy + description: Release tag (release/candy-machine-core@0.2.1) or (release/candy-guard@0.2.1) or commit to deploy required: true type: string - default: release/1.0.0 + default: release/candy-guard@0.2.1 dry_run: description: Dry run required: false @@ -63,7 +63,7 @@ jobs: - name: Git checkout uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.git_ref }} + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV From 6857257828e2a26a3a68229a94c9d6596d21d7fc Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 09:17:53 -0700 Subject: [PATCH 16/31] fix action syntax --- .github/workflows/deploy-program.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index bd7cbf8..2f8b0fb 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -131,7 +131,7 @@ jobs: name: program-builds-${{ inputs.git_ref }} - name: Deploy Program - if: !github.event.inputs.dry_run + if: github.event.inputs.dry_run == 'false' run: | echo "Deploying ${{ inputs.program }} to ${{ inputs.cluster }}" From 1024cd3968184d65da9a4111814f58051af305e5 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 10:22:14 -0700 Subject: [PATCH 17/31] allow latest tagging --- .github/workflows/create-release.yml | 13 ++++++- .github/workflows/deploy-program.yml | 51 +++++++++++++++++++++------- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index b2b7768..ee64246 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -114,4 +114,15 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ./programs/.bin/${{ env.PROGRAM_NAME }}.so asset_name: ${{ inputs.program }}.so - asset_content_type: application/octet-stream \ No newline at end of file + asset_content_type: application/octet-stream + + - name: Update latest tag + uses: actions/github-script@v5 + with: + script: | + github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'refs/tags/release/${{ inputs.program }}@latest', + sha: '${{ inputs.git_ref }}' + }); \ No newline at end of file diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 2f8b0fb..577cb33 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -3,10 +3,14 @@ name: Deploy Program on: workflow_dispatch: inputs: - program: - description: Program + git_ref: + description: Release tag (e.g. release/candy-machine-core@latest , release/candy-guard@0.2.1) or commit to deploy required: true - default: candy-guard + type: string + default: release/candy-guard@latest + program: + description: Program (required if not a release tag) + required: false type: choice options: - candy-guard @@ -23,11 +27,6 @@ on: - sonic-testnet - eclipse-mainnet - eclipse-devnet - git_ref: - description: Release tag (release/candy-machine-core@0.2.1) or (release/candy-guard@0.2.1) or commit to deploy - required: true - type: string - default: release/candy-guard@0.2.1 dry_run: description: Dry run required: false @@ -38,10 +37,36 @@ env: CACHE: true jobs: + check_tag: + name: 'Check tag' + runs-on: ubuntu-latest + outputs: + program: ${{ steps.set_program.outputs.program }} + type: ${{ steps.set_program.outputs.type }} + steps: + - name: Check tag + id: set_program + run: | + if [[ "${{ inputs.git_ref }}" == "release/candy-guard@*" ]]; then + echo program="candy-guard" >> $GITHUB_OUTPUT + echo type="release" >> $GITHUB_OUTPUT + else if [[ "${{ inputs.git_ref }}" == "release/candy-machine-core@*" ]]; then + echo program="candy-machine-core" >> $GITHUB_OUTPUT + echo type="relase" >> $GITHUB_OUTPUT + else if [[ "${{ inputs.git_ref }}" == "candy-machine-core" || "${{ inputs.git_ref }}" == "candy-guard" ]]; then + echo program="${{ inputs.program }}" >> $GITHUB_OUTPUT + echo type="ref" >> $GITHUB_OUTPUT + else + echo "Non-release tag and program not specified" + exit 1; + fi + build_programs: name: Programs uses: ./.github/workflows/build-programs.yml secrets: inherit + needs: check_tag + if: needs.check_tag.outputs.type == 'ref' with: git_ref: ${{ inputs.git_ref }} @@ -56,7 +81,7 @@ jobs: deploy_program: name: Program / Deploy runs-on: ubuntu-latest - needs: test_js + needs: [test_js, check_tag] permissions: contents: write steps: @@ -111,7 +136,7 @@ jobs: else if [[ "${{ inputs.cluster }}" == "eclipse"* ]]; then echo ${{ secrets.CORE_CANDY_MACHINE_ECLIPSE_DEPLOY_KEY }} > ./deploy-key.json fi - if [ "${{ inputs.program }}" == "candy-guard" ]; then + if [ "${{ needs.check_tag.outputs.program }}" == "candy-guard" ]; then if [ ! -e "./deploy-key.json" ]; then echo ${{ secrets.CORE_CANDY_GUARD_DEPLOY_KEY }} > ./deploy-key.json fi @@ -133,7 +158,7 @@ jobs: - name: Deploy Program if: github.event.inputs.dry_run == 'false' run: | - echo "Deploying ${{ inputs.program }} to ${{ inputs.cluster }}" + echo "Deploying ${{ needs.check_tag.outputs.program }} to ${{ inputs.cluster }}" solana -v program deploy ./programs/.bin/${{ env.PROGRAM_NAME }}.so \ -u ${{ env.RPC }} \ @@ -143,7 +168,7 @@ jobs: --use-rpc - name: Publish crate - working-directory: ./programs/${{ inputs.program }}/program + working-directory: ./programs/${{ needs.check_tag.outputs.program }}/program if: github.event.inputs.publish_crate == 'true' && github.event.inputs.cluster == 'mainnet-beta' run: | git stash @@ -163,6 +188,6 @@ jobs: github.rest.git.createRef({ owner: context.repo.owner, repo: context.repo.repo, - ref: 'refs/tags/${{ inputs.program }}-${{ inputs.cluster }}', + ref: 'refs/tags/${{ needs.check_tag.outputs.program }}-${{ inputs.cluster }}', sha: '${{ inputs.git_ref }}' }); From 2e0888d7457f29fa0cad82e04cbdbff91e90e6e2 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 12:58:38 -0700 Subject: [PATCH 18/31] use actual sha for tag --- .github/workflows/create-release.yml | 2 +- .github/workflows/deploy-program.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index ee64246..5d3a144 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -124,5 +124,5 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, ref: 'refs/tags/release/${{ inputs.program }}@latest', - sha: '${{ inputs.git_ref }}' + sha: '${{ github.sha }}' }); \ No newline at end of file diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 577cb33..ce42b92 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -189,5 +189,5 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, ref: 'refs/tags/${{ needs.check_tag.outputs.program }}-${{ inputs.cluster }}', - sha: '${{ inputs.git_ref }}' + sha: '${{ github.sha }}' }); From 2ca16e7f114c898fb977976f9adf50131be4d094 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 14:27:26 -0700 Subject: [PATCH 19/31] write valid bash elif --- .github/workflows/deploy-program.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index ce42b92..02ad624 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -47,13 +47,13 @@ jobs: - name: Check tag id: set_program run: | - if [[ "${{ inputs.git_ref }}" == "release/candy-guard@*" ]]; then + if [[ "${{ inputs.git_ref }}" =~ ^release/candy-guard@* ]]; then echo program="candy-guard" >> $GITHUB_OUTPUT echo type="release" >> $GITHUB_OUTPUT - else if [[ "${{ inputs.git_ref }}" == "release/candy-machine-core@*" ]]; then + elif [[ "${{ inputs.git_ref }}" =~ ^release/candy-machine-core@* ]]; then echo program="candy-machine-core" >> $GITHUB_OUTPUT echo type="relase" >> $GITHUB_OUTPUT - else if [[ "${{ inputs.git_ref }}" == "candy-machine-core" || "${{ inputs.git_ref }}" == "candy-guard" ]]; then + elif [[ "${{ inputs.git_ref }}" == "candy-machine-core" || "${{ inputs.git_ref }}" == "candy-guard" ]]; then echo program="${{ inputs.program }}" >> $GITHUB_OUTPUT echo type="ref" >> $GITHUB_OUTPUT else @@ -115,17 +115,17 @@ jobs: # We do this if waterfall because github actions does not allow dynamic access to secrets if [ "${{ inputs.cluster }}" == "devnet" ]; then echo RPC=${{ secrets.DEVNET_RPC }} >> $GITHUB_ENV - else if [ "${{ inputs.cluster }}" == "mainnet-beta" ]; then + elif [ "${{ inputs.cluster }}" == "mainnet-beta" ]; then echo RPC=${{ secrets.MAINNET_RPC }} >> $GITHUB_ENV - else if [ "${{ inputs.cluster }}" == "sonic-devnet" ]; then + elif [ "${{ inputs.cluster }}" == "sonic-devnet" ]; then echo RPC=${{ secrets.SONIC_DEVNET_RPC }} >> $GITHUB_ENV - else if [ "${{ inputs.cluster }}" == "sonic-testnet" ]; then + elif [ "${{ inputs.cluster }}" == "sonic-testnet" ]; then echo RPC=${{ secrets.SONIC_TESTNET_RPC }} >> $GITHUB_ENV - else if [ "${{ inputs.cluster }}" == "eclipse-devnet" ]; then + elif [ "${{ inputs.cluster }}" == "eclipse-devnet" ]; then echo RPC=${{ secrets.ECLIPSE_DEVNET_RPC }} >> $GITHUB_ENV - else if [ "${{ inputs.cluster }}" == "eclipse-testnet" ]; then + elif [ "${{ inputs.cluster }}" == "eclipse-testnet" ]; then echo RPC=${{ secrets.ECLIPSE_TESTNET_RPC }} >> $GITHUB_ENV - else if [ "${{ inputs.cluster }}" == "eclipse-mainnet" ]; then + elif [ "${{ inputs.cluster }}" == "eclipse-mainnet" ]; then echo RPC=${{ secrets.ECLIPSE_MAINNET_RPC }} >> $GITHUB_ENV fi @@ -133,7 +133,7 @@ jobs: run: | if [[ "${{ inputs.cluster }}" == "sonic"* ]]; then echo ${{ secrets.CORE_CANDY_MACHINE_SONIC_DEPLOY_KEY }} > ./deploy-key.json - else if [[ "${{ inputs.cluster }}" == "eclipse"* ]]; then + elif [[ "${{ inputs.cluster }}" == "eclipse"* ]]; then echo ${{ secrets.CORE_CANDY_MACHINE_ECLIPSE_DEPLOY_KEY }} > ./deploy-key.json fi if [ "${{ needs.check_tag.outputs.program }}" == "candy-guard" ]; then From 2125fe10464d18a8a9e0249c6052db2ee7080fb7 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 21:28:44 -0700 Subject: [PATCH 20/31] run deploy if type is ref --- .github/workflows/deploy-program.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 02ad624..25ca774 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -82,6 +82,7 @@ jobs: name: Program / Deploy runs-on: ubuntu-latest needs: [test_js, check_tag] + if: needs.test_js.result == 'success' || (needs.check_tag.result == 'success' && needs.check_tag.outputs.type == 'ref') permissions: contents: write steps: From 5d32f230d5a0b4bc8b5858184cd5694112e80cae Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 22:35:19 -0700 Subject: [PATCH 21/31] download release artifact --- .github/workflows/deploy-program.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 25ca774..9d51de9 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -153,9 +153,20 @@ jobs: - name: Download Program Builds uses: actions/download-artifact@v4 + if: needs.check_tag.outputs.type == 'ref' with: name: program-builds-${{ inputs.git_ref }} + - name: Download release asset + uses: dsaltares/fetch-gh-release-asset@master + if: needs.check_tag.outputs.type == 'release' + with: + repo: ${{ github.repository }} + version: ${{ inputs.git_ref }} + target: './programs/.bin/${{ env.PROGRAM_NAME }}.so' + file: ${{ env.PROGRAM_NAME }}.so + token: ${{ secrets.GITHUB_TOKEN }} + - name: Deploy Program if: github.event.inputs.dry_run == 'false' run: | From eb478ececd980afe170587804e78c8e18b3717cf Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 22:41:37 -0700 Subject: [PATCH 22/31] fix typo --- .github/workflows/deploy-program.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 9d51de9..13d58ca 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -52,7 +52,7 @@ jobs: echo type="release" >> $GITHUB_OUTPUT elif [[ "${{ inputs.git_ref }}" =~ ^release/candy-machine-core@* ]]; then echo program="candy-machine-core" >> $GITHUB_OUTPUT - echo type="relase" >> $GITHUB_OUTPUT + echo type="release" >> $GITHUB_OUTPUT elif [[ "${{ inputs.git_ref }}" == "candy-machine-core" || "${{ inputs.git_ref }}" == "candy-guard" ]]; then echo program="${{ inputs.program }}" >> $GITHUB_OUTPUT echo type="ref" >> $GITHUB_OUTPUT @@ -82,7 +82,7 @@ jobs: name: Program / Deploy runs-on: ubuntu-latest needs: [test_js, check_tag] - if: needs.test_js.result == 'success' || (needs.check_tag.result == 'success' && needs.check_tag.outputs.type == 'ref') + if: needs.test_js.result == 'success' || (needs.check_tag.result == 'success' && needs.check_tag.outputs.type == 'release') permissions: contents: write steps: From 55ffe19149531387bae008abcc1282382079feaf Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 22:57:24 -0700 Subject: [PATCH 23/31] run deploy even if dep is skipped --- .github/workflows/deploy-program.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 13d58ca..479b14f 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -82,7 +82,7 @@ jobs: name: Program / Deploy runs-on: ubuntu-latest needs: [test_js, check_tag] - if: needs.test_js.result == 'success' || (needs.check_tag.result == 'success' && needs.check_tag.outputs.type == 'release') + if: always() && (needs.test_js.result == 'success' || needs.test_js.result == 'skipped') permissions: contents: write steps: From aeee89cc811c1176f0b03e6e565e6282e673fe1c Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 23:06:55 -0700 Subject: [PATCH 24/31] use tag name for release --- .github/workflows/deploy-program.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 479b14f..fca1ca2 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -162,7 +162,7 @@ jobs: if: needs.check_tag.outputs.type == 'release' with: repo: ${{ github.repository }} - version: ${{ inputs.git_ref }} + version: 'tags/${{ inputs.git_ref }}' target: './programs/.bin/${{ env.PROGRAM_NAME }}.so' file: ${{ env.PROGRAM_NAME }}.so token: ${{ secrets.GITHUB_TOKEN }} From 64e85139cc85513a4fe5bc176bcf1d05bba2cc9a Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 23:38:00 -0700 Subject: [PATCH 25/31] find release via tag --- .github/workflows/deploy-program.yml | 44 +++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index fca1ca2..4c8cc6f 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -156,16 +156,46 @@ jobs: if: needs.check_tag.outputs.type == 'ref' with: name: program-builds-${{ inputs.git_ref }} - + - name: Download release asset - uses: dsaltares/fetch-gh-release-asset@master + uses: actions/github-script@v5 + id: get_release if: needs.check_tag.outputs.type == 'release' with: - repo: ${{ github.repository }} - version: 'tags/${{ inputs.git_ref }}' - target: './programs/.bin/${{ env.PROGRAM_NAME }}.so' - file: ${{ env.PROGRAM_NAME }}.so - token: ${{ secrets.GITHUB_TOKEN }} + script: | + const tag = ${{ inputs.git_ref }}; + const assetName = ${{ env.PROGRAM_NAME }}.so; + + // Fetch the release associated with the tag + const release = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: tag + }); + + if (release.status !== 200) { + throw new Error(`Failed to fetch release for tag ${tag}`); + } + + // Find the specific asset by name + const asset = release.data.assets.find(asset => asset.name === assetName); + if (!asset) { + throw new Error(`Asset ${assetName} not found in release tagged ${tag}`); + } + + return { + download_url: asset.url, + asset_name: asset.name + }; + + - name: Download the Selected Asset + if: needs.check_tag.outputs.type == 'release' + run: | + mkdir -p ${{ github.workspace }}/programs/.bin + curl -L -o ${{ github.workspace }}/programs/.bin/${{ steps.get_release.outputs.asset_name }} \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/octet-stream" \ + "${{ steps.get_release.outputs.download_url }}" - name: Deploy Program if: github.event.inputs.dry_run == 'false' From d3cc224a0612efe9e0aec8db07ba26abc89767b4 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Thu, 12 Sep 2024 23:45:48 -0700 Subject: [PATCH 26/31] syntax --- .github/workflows/deploy-program.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 4c8cc6f..944edd2 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -163,8 +163,8 @@ jobs: if: needs.check_tag.outputs.type == 'release' with: script: | - const tag = ${{ inputs.git_ref }}; - const assetName = ${{ env.PROGRAM_NAME }}.so; + const tag = "${{ inputs.git_ref }}"; + const assetName = "${{ env.PROGRAM_NAME }}.so"; // Fetch the release associated with the tag const release = await github.rest.repos.getReleaseByTag({ From 9a7b81ea2db530230d656e1cd0d40c98ad09be68 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Fri, 13 Sep 2024 00:03:40 -0700 Subject: [PATCH 27/31] remove convenience latest tag --- .github/workflows/create-release.yml | 22 +++++++++++----------- .github/workflows/deploy-program.yml | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 5d3a144..4c75965 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -113,16 +113,16 @@ jobs: with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ./programs/.bin/${{ env.PROGRAM_NAME }}.so - asset_name: ${{ inputs.program }}.so + asset_name: ${{ env.PROGRAM_NAME }}.so asset_content_type: application/octet-stream - - name: Update latest tag - uses: actions/github-script@v5 - with: - script: | - github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: 'refs/tags/release/${{ inputs.program }}@latest', - sha: '${{ github.sha }}' - }); \ No newline at end of file + # - name: Update latest tag + # uses: actions/github-script@v5 + # with: + # script: | + # github.rest.git.createRef({ + # owner: context.repo.owner, + # repo: context.repo.repo, + # ref: 'refs/tags/release/${{ inputs.program }}@latest', + # sha: '${{ github.sha }}' + # }); \ No newline at end of file diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 944edd2..72010d7 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -4,10 +4,10 @@ on: workflow_dispatch: inputs: git_ref: - description: Release tag (e.g. release/candy-machine-core@latest , release/candy-guard@0.2.1) or commit to deploy + description: Release tag (release/candy-machine-core@0.2.2 or release/candy-guard@0.2.1) or commit to deploy required: true type: string - default: release/candy-guard@latest + default: release/candy-guard@0.2.2 program: description: Program (required if not a release tag) required: false @@ -192,7 +192,7 @@ jobs: if: needs.check_tag.outputs.type == 'release' run: | mkdir -p ${{ github.workspace }}/programs/.bin - curl -L -o ${{ github.workspace }}/programs/.bin/${{ steps.get_release.outputs.asset_name }} \ + curl -L -o ${{ github.workspace }}/programs/.bin/${{ env.PROGRAM_NAME }}.so \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ -H "Accept: application/octet-stream" \ "${{ steps.get_release.outputs.download_url }}" From 83e8d67e3ed1071fba818e6ad5cf009e2bd190c8 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Fri, 13 Sep 2024 00:23:44 -0700 Subject: [PATCH 28/31] log --- .github/workflows/deploy-program.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 72010d7..622e3e6 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -183,6 +183,8 @@ jobs: throw new Error(`Asset ${assetName} not found in release tagged ${tag}`); } + console.log(asset); + return { download_url: asset.url, asset_name: asset.name From aee77d46b75320dcdd7f73ac328efddd5d0a47f6 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Fri, 13 Sep 2024 00:28:17 -0700 Subject: [PATCH 29/31] correctly set output --- .github/workflows/deploy-program.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 622e3e6..2fa0b2d 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -163,6 +163,7 @@ jobs: if: needs.check_tag.outputs.type == 'release' with: script: | + const core = require('@actions/core'); const tag = "${{ inputs.git_ref }}"; const assetName = "${{ env.PROGRAM_NAME }}.so"; @@ -177,18 +178,12 @@ jobs: throw new Error(`Failed to fetch release for tag ${tag}`); } - // Find the specific asset by name const asset = release.data.assets.find(asset => asset.name === assetName); if (!asset) { throw new Error(`Asset ${assetName} not found in release tagged ${tag}`); } - console.log(asset); - - return { - download_url: asset.url, - asset_name: asset.name - }; + core.setOutput("url", asset.url); - name: Download the Selected Asset if: needs.check_tag.outputs.type == 'release' @@ -197,7 +192,7 @@ jobs: curl -L -o ${{ github.workspace }}/programs/.bin/${{ env.PROGRAM_NAME }}.so \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ -H "Accept: application/octet-stream" \ - "${{ steps.get_release.outputs.download_url }}" + "${{ steps.get_release.outputs.url }}" - name: Deploy Program if: github.event.inputs.dry_run == 'false' From 8a78a4af7bb0eb68dcdb1f80afd8b2a5f2321bbf Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Fri, 13 Sep 2024 00:31:56 -0700 Subject: [PATCH 30/31] don't timport core --- .github/workflows/deploy-program.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 2fa0b2d..eac1a33 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -163,7 +163,6 @@ jobs: if: needs.check_tag.outputs.type == 'release' with: script: | - const core = require('@actions/core'); const tag = "${{ inputs.git_ref }}"; const assetName = "${{ env.PROGRAM_NAME }}.so"; From 5cc1092c4ca1f488359780cf9c3fd0a7aee70e56 Mon Sep 17 00:00:00 2001 From: Nhan Phan Date: Sat, 14 Sep 2024 10:31:40 -0700 Subject: [PATCH 31/31] point tag to right place --- .github/workflows/deploy-program.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index eac1a33..4e4eab9 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -219,13 +219,21 @@ jobs: git reset --soft HEAD~1 git stash pop - - name: Create tag + - name: Create env tag uses: actions/github-script@v5 with: script: | + const refData = await github.rest.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'tags/${{ inputs.git_ref }}' + }); + if (refData.status !== 200) { + throw new Error('Failed to fetch existing tag'); + } github.rest.git.createRef({ owner: context.repo.owner, repo: context.repo.repo, ref: 'refs/tags/${{ needs.check_tag.outputs.program }}-${{ inputs.cluster }}', - sha: '${{ github.sha }}' + sha: refData.data.object.sha });