From 5a4086738bfcb2f357f8c1ad8f7c18893f8e592d Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Thu, 24 Jun 2021 19:07:29 -0500 Subject: [PATCH 1/5] reservation list fix for scaling (#70) --- js/packages/common/src/actions/metadata.ts | 4 + rust/Cargo.lock | 1005 +++++++++++++++-- .../program/src/processor/redeem_bid.rs | 92 +- rust/token-metadata/program/src/error.rs | 8 + .../token-metadata/program/src/instruction.rs | 16 +- rust/token-metadata/program/src/processor.rs | 53 +- rust/token-metadata/program/src/state.rs | 31 +- rust/token-metadata/test/src/main.rs | 41 +- 8 files changed, 1091 insertions(+), 159 deletions(-) diff --git a/js/packages/common/src/actions/metadata.ts b/js/packages/common/src/actions/metadata.ts index 2fc42bf..7950cb4 100644 --- a/js/packages/common/src/actions/metadata.ts +++ b/js/packages/common/src/actions/metadata.ts @@ -148,17 +148,20 @@ export class ReservationList { /// What supply counter was on master_edition when this reservation was created. supplySnapshot: BN | null; reservations: Reservation[]; + totalReservationSpots: BN; constructor(args: { key: MetadataKey; masterEdition: PublicKey; supplySnapshot: BN | null; reservations: Reservation[]; + totalReservationSpots: BN; }) { this.key = MetadataKey.EditionV1; this.masterEdition = args.masterEdition; this.supplySnapshot = args.supplySnapshot; this.reservations = args.reservations; + this.totalReservationSpots = args.totalReservationSpots; } } @@ -402,6 +405,7 @@ export const METADATA_SCHEMA = new Map([ ['masterEdition', 'pubkey'], ['supplySnapshot', { kind: 'option', type: 'u64' }], ['reservations', [Reservation]], + ['totalReservationSpots', 'u64'], ], }, ], diff --git a/rust/Cargo.lock b/rust/Cargo.lock index fc5f079..85f28dd 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -40,6 +40,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "anyhow" version = "1.0.40" @@ -113,6 +122,18 @@ dependencies = [ "serde", ] +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + [[package]] name = "base64" version = "0.12.3" @@ -282,6 +303,12 @@ dependencies = [ "iovec", ] +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + [[package]] name = "bytes" version = "1.0.1" @@ -311,12 +338,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.49" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e450b8da92aa6f274e7c6437692f9f2ce6d701fb73bacfcf87897b3f89a4c20e" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" dependencies = [ "jobserver", - "num_cpus", ] [[package]] @@ -347,13 +373,28 @@ dependencies = [ [[package]] name = "chrono-humanize" -version = "0.1.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8164ae3089baf04ff71f32aeb70213283dcd236dce8bc976d00b17a458f5f71c" +checksum = "2eddc119501d583fd930cb92144e605f44e0252c38dd89d9247fffa1993375cb" dependencies = [ "chrono", ] +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -376,12 +417,60 @@ dependencies = [ "unreachable", ] +[[package]] +name = "console" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c0994e656bba7b922d8dd1245db90672ffb701e684e45be58f20719d69abc5a" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "termios", + "unicode-width", + "winapi 0.3.9", + "winapi-util", +] + +[[package]] +name = "console" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "unicode-width", + "winapi 0.3.9", +] + [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + [[package]] name = "cpufeatures" version = "0.1.4" @@ -605,6 +694,17 @@ dependencies = [ "syn 1.0.72", ] +[[package]] +name = "dialoguer" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aa86af7b19b40ef9cbef761ed411a49f0afa06b7b6dcd3dfe2f96a3c546138" +dependencies = [ + "console 0.11.3", + "lazy_static", + "tempfile", +] + [[package]] name = "digest" version = "0.8.1" @@ -632,6 +732,33 @@ dependencies = [ "walkdir", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + [[package]] name = "ed25519" version = "1.1.1" @@ -688,6 +815,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.28" @@ -787,6 +920,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -797,12 +945,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" - [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -1046,6 +1188,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hidapi" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e07da7e8614133e88b3a93b7352eb3729e3ccd82d5ab661adf23bef1761bf8" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "hmac" version = "0.7.1" @@ -1056,6 +1209,16 @@ dependencies = [ "digest 0.8.1", ] +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.9.0" @@ -1144,7 +1307,7 @@ dependencies = [ "httpdate", "itoa", "pin-project", - "socket2", + "socket2 0.4.0", "tokio 1.6.1", "tower-service", "tracing", @@ -1187,6 +1350,27 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4" +dependencies = [ + "console 0.14.1", + "lazy_static", + "number_prefix", + "regex", +] + +[[package]] +name = "input_buffer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +dependencies = [ + "bytes 0.5.6", +] + [[package]] name = "instant" version = "0.1.9" @@ -1226,38 +1410,6 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" -[[package]] -name = "jemalloc-ctl" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c502a5ff9dd2924f1ed32ba96e3b65735d837b4bfd978d3161b1702e66aca4b7" -dependencies = [ - "jemalloc-sys", - "libc", - "paste", -] - -[[package]] -name = "jemalloc-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45" -dependencies = [ - "cc", - "fs_extra", - "libc", -] - -[[package]] -name = "jemallocator" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69" -dependencies = [ - "jemalloc-sys", - "libc", -] - [[package]] name = "jobserver" version = "0.1.22" @@ -1276,6 +1428,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonrpc-core" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4467ab6dfa369b69e52bd0692e480c4d117410538526a57a304a0f2250fd95e" +dependencies = [ + "futures 0.3.15", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "keccak" version = "0.1.0" @@ -1333,6 +1500,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + [[package]] name = "lock_api" version = "0.3.4" @@ -1485,6 +1658,24 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "native-tls" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "net2" version = "0.2.37" @@ -1496,6 +1687,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nix" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -1578,6 +1781,12 @@ dependencies = [ "syn 1.0.72", ] +[[package]] +name = "number_prefix" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" + [[package]] name = "object" version = "0.23.0" @@ -1589,6 +1798,9 @@ name = "once_cell" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +dependencies = [ + "parking_lot 0.11.1", +] [[package]] name = "opaque-debug" @@ -1602,6 +1814,39 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + +[[package]] +name = "openssl-sys" +version = "0.9.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ouroboros" version = "0.5.1" @@ -1635,6 +1880,16 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api 0.3.4", + "parking_lot_core 0.7.2", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -1661,6 +1916,20 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall 0.1.57", + "smallvec 1.6.1", + "winapi 0.3.9", +] + [[package]] name = "parking_lot_core" version = "0.8.3" @@ -1676,22 +1945,12 @@ dependencies = [ ] [[package]] -name = "paste" -version = "0.1.18" +name = "pbkdf2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" dependencies = [ - "paste-impl", - "proc-macro-hack", -] - -[[package]] -name = "paste-impl" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -dependencies = [ - "proc-macro-hack", + "crypto-mac 0.8.0", ] [[package]] @@ -1709,6 +1968,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "pin-project" version = "1.0.7" @@ -1956,6 +2224,16 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.3", + "redox_syscall 0.2.8", +] + [[package]] name = "regex" version = "1.5.4" @@ -2020,32 +2298,48 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.12" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", - "lazy_static", "libc", + "once_cell", "spin", "untrusted", "web-sys", "winapi 0.3.9", ] +[[package]] +name = "rpassword" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99371657d3c8e4d816fb6221db98fa408242b0b53bac08f8676a41f8554fe99f" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "rustc-demangle" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", ] [[package]] @@ -2082,6 +2376,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2118,13 +2422,45 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", ] [[package]] @@ -2133,6 +2469,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.126" @@ -2185,6 +2530,30 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + [[package]] name = "sha2" version = "0.8.2" @@ -2258,6 +2627,17 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + [[package]] name = "socket2" version = "0.4.0" @@ -2269,10 +2649,33 @@ dependencies = [ ] [[package]] -name = "solana-banks-client" -version = "1.6.11" +name = "solana-account-decoder" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9977706df3b1d69e91eb4d0c53d60bd262f96de4116b606afea8728d33e8072e" +checksum = "e49c9e97fbaa91220924064f23ce367c02af1a3bc8cc514f84168b47cbb075c7" +dependencies = [ + "Inflector", + "base64 0.12.3", + "bincode", + "bs58", + "bv", + "lazy_static", + "serde", + "serde_derive", + "serde_json", + "solana-config-program", + "solana-sdk", + "solana-vote-program", + "spl-token", + "thiserror", + "zstd", +] + +[[package]] +name = "solana-banks-client" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "570ed9c8e78fabbd2f93140f731c9361b7aa6292341e03811e5b6d3ed2470c5a" dependencies = [ "bincode", "borsh", @@ -2289,9 +2692,9 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066bd7c2655141b095cfe25afc1aa76879254e0fe02ac212b858449cbddf275" +checksum = "f3b253e4d53eef373ab186829988a9ad4f7bfce886cf0e1fade1ad866c294df2" dependencies = [ "mio 0.7.11", "serde", @@ -2301,9 +2704,9 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "950c7a4061aaf4f79293caf06c12f639a4a55cfd5f3ec9f5323be869e4426ef9" +checksum = "a350a47e84f4e28a28e985e96150a222559b0e23f71a3b34760b7463091d0877" dependencies = [ "bincode", "futures 0.3.15", @@ -2321,9 +2724,9 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba7fcf2994963a758c97b6c9d205036ea9b1baf84227398f9bbc47d25877e00" +checksum = "7305c923737fc515d88454dd991b8c21c5e9270f72db3cd52da888d4d233fe07" dependencies = [ "bincode", "byteorder", @@ -2340,10 +2743,75 @@ dependencies = [ ] [[package]] -name = "solana-config-program" -version = "1.6.11" +name = "solana-clap-utils" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9341b8d8c32b970b6aa3e4a58795f5120154b3896b7231b830a5a9a13bc8083" +checksum = "29dca103fa63d4fa564c719d2f043feb4a116722362cbcbf027d4404fc65e7b3" +dependencies = [ + "chrono", + "clap", + "rpassword", + "solana-remote-wallet", + "solana-sdk", + "thiserror", + "tiny-bip39", + "uriparse", + "url", +] + +[[package]] +name = "solana-cli-config" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f22f4acf359df9e859878fc456bb98cfd3d008ee239a6300259c439a2356d1c9" +dependencies = [ + "dirs-next", + "lazy_static", + "serde", + "serde_derive", + "serde_yaml", + "url", +] + +[[package]] +name = "solana-client" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a149653548051cdcfdfb3494ad46a9810e647cbf004b0f65dccafd92eec152" +dependencies = [ + "base64 0.13.0", + "bincode", + "bs58", + "clap", + "indicatif", + "jsonrpc-core", + "log", + "net2", + "rayon", + "reqwest", + "semver 0.11.0", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-clap-utils", + "solana-faucet", + "solana-net-utils", + "solana-sdk", + "solana-transaction-status", + "solana-version", + "solana-vote-program", + "thiserror", + "tokio 1.6.1", + "tungstenite", + "url", +] + +[[package]] +name = "solana-config-program" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056a9300b5e2665d0f6cf1dfbc5f9778ec616485adbe35af01fec234e1f9b288" dependencies = [ "bincode", "chrono", @@ -2356,9 +2824,9 @@ dependencies = [ [[package]] name = "solana-crate-features" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d62c8e8a526b62e3cefe51f079cc48585a5026d1d5a86a99ce38d782273027f" +checksum = "887ff45393b195ab7d93cdc94511c7cc2319a379b659e8008de77028c2326846" dependencies = [ "backtrace", "bytes 0.4.12", @@ -2371,6 +2839,7 @@ dependencies = [ "rand_chacha 0.2.2", "regex-syntax", "reqwest", + "ring", "serde", "syn 0.15.44", "syn 1.0.72", @@ -2379,10 +2848,33 @@ dependencies = [ ] [[package]] -name = "solana-frozen-abi" -version = "1.6.11" +name = "solana-faucet" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439fb97ca6bf4098973413e806071116a0d87dcb197c0c2ae01f71a1588d6dcc" +checksum = "981359d3f0f420dfa6d5eee4d33357d619817425a64ad090f2f88e521c9a24bd" +dependencies = [ + "bincode", + "byteorder", + "clap", + "log", + "serde", + "serde_derive", + "solana-clap-utils", + "solana-cli-config", + "solana-logger", + "solana-metrics", + "solana-sdk", + "solana-version", + "spl-memo", + "thiserror", + "tokio 1.6.1", +] + +[[package]] +name = "solana-frozen-abi" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc00a9f7c3eb2fb8687d34ce6d8672fbf7bd8f67002a5f75ccd6f6c4e8cd8a91" dependencies = [ "bs58", "bv", @@ -2400,11 +2892,10 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4b1844deb909101f83735504f41f7ce539fe0c448fa52bda1a7d2233aaefc3" +checksum = "bc381a29ab68515e69dcfad633ab78dd98d83c0b959c2cae9a9a98df6e265acf" dependencies = [ - "lazy_static", "proc-macro2 1.0.27", "quote 1.0.9", "rustc_version", @@ -2413,9 +2904,9 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01545ecd73ca356dd3d31db303e0ecc1989b67a1561dce0c8d8ae2d4c536562d" +checksum = "62f8e4921602f61681d8d29d2606d4f8e1c848d4f6b9964813bfc1b457dfd7ce" dependencies = [ "env_logger", "lazy_static", @@ -2424,12 +2915,10 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ddf22ce1f47ebc723bceec387100a52a27ed6a807092ac90ecc70189bb10ce2" +checksum = "958b8dab77246d9c71c78a4e9c5c31a1092dafb70294ce0334acf572442b9d84" dependencies = [ - "jemalloc-ctl", - "jemallocator", "log", "solana-metrics", "solana-sdk", @@ -2437,9 +2926,9 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2aa796e88a5fc8628474a55929cc3d8c5dd2d10900cd591917eac22de86a5a" +checksum = "77968c10909ef49d7a3cccc3ca8de9415e8750411ad917889ef8ba9ae8692d94" dependencies = [ "env_logger", "gethostname", @@ -2450,10 +2939,32 @@ dependencies = [ ] [[package]] -name = "solana-program" -version = "1.6.11" +name = "solana-net-utils" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ac6cafe3bb91070644839600e65b185292e285abc31ac626ddab6a79c0cc18e" +checksum = "9de765ffc78c8068b9efce0ff08b399a94105b004588c90a4b0835995dfc4712" +dependencies = [ + "bincode", + "clap", + "log", + "nix", + "rand 0.7.3", + "serde", + "serde_derive", + "socket2 0.3.19", + "solana-clap-utils", + "solana-logger", + "solana-sdk", + "solana-version", + "tokio 1.6.1", + "url", +] + +[[package]] +name = "solana-program" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9c454274436aac77286369e35835fafa1f79d1da1c4b7a1c662b2c41705f77" dependencies = [ "bincode", "blake3", @@ -2485,9 +2996,9 @@ dependencies = [ [[package]] name = "solana-program-test" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c6637e910327d7dbcd23b121437eb7bc64f98bc1080bc50db944f015f21d43" +checksum = "4cd5922e4a9a7437a19ff5d79f60bac64ea2636a365586cb3511f4d562a20cf7" dependencies = [ "async-trait", "base64 0.12.3", @@ -2511,19 +3022,40 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e0a2dbe5f36a32c5f4c366bde65ac10f516693f0342da36dd6b3daf012ba50f" +checksum = "d837161f598afd30c445a7f9c39d640cd4cdf362c30062ed0831b71daaf63a45" dependencies = [ "lazy_static", "num_cpus", ] [[package]] -name = "solana-runtime" -version = "1.6.11" +name = "solana-remote-wallet" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4ca322a5fbcc3b647de773d8b8f30659b0b956f0226ec1a8fadc08159cc8be" +checksum = "24087b3048534e154db68c4cdd8233f3a35dd6f860d97e5ad1008c44e0d311f5" +dependencies = [ + "base32", + "console 0.14.1", + "dialoguer", + "hidapi", + "log", + "num-derive", + "num-traits", + "parking_lot 0.10.2", + "qstring", + "semver 0.9.0", + "solana-sdk", + "thiserror", + "uriparse", +] + +[[package]] +name = "solana-runtime" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224e6ef5ea772d9636493dd4fd196cf3b167ff73b7d54e7f591b2f2d452c70d6" dependencies = [ "arrayref", "bincode", @@ -2572,9 +3104,9 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a978bfd23136d5af402c5b932ffc6cb5a9a047d3dddc2522b729b270b8df546" +checksum = "339cc1a9d437ae2f5f05d2d678c8f6d19ea6cf57cbdf719b7726156d294487bd" dependencies = [ "assert_matches", "bincode", @@ -2596,7 +3128,7 @@ dependencies = [ "memmap2", "num-derive", "num-traits", - "pbkdf2", + "pbkdf2 0.6.0", "qstring", "rand 0.7.3", "rand_chacha 0.2.2", @@ -2621,9 +3153,9 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9205b47aa8e2474d014033375dea8734f14038fc860feed2ef654ee17c73353" +checksum = "85ee9c0af66098ec40bf9012b7910c8cdb1ce8b95fc9fad90e6a0cbe692a48fe" dependencies = [ "bs58", "proc-macro2 1.0.27", @@ -2634,9 +3166,9 @@ dependencies = [ [[package]] name = "solana-secp256k1-program" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eade5b09385c7139a8aab6420d76087a4ef6e47a03343cb70e77a50d6567eb62" +checksum = "2666ffed520bff7d0eb6747da156435def7f97341634e72af39fefc0d27496f1" dependencies = [ "bincode", "digest 0.9.0", @@ -2649,9 +3181,9 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "1.6.11" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a42ec32e879eb261fa847f659cd5631111c7a4b824bc29d90f4d9d497b93774" +checksum = "42f9e0309f95b3160c4961f11a3940d98126a86a7410b04bf88ff79448984257" dependencies = [ "bincode", "log", @@ -2670,10 +3202,50 @@ dependencies = [ ] [[package]] -name = "solana-vote-program" -version = "1.6.11" +name = "solana-transaction-status" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c69608668f8738cbaa3a25d15943dd202448b6eafe5d480326a91bd87f0eac" +checksum = "952083e52e835be5c0f10707d9019df84f3be5f0c524391848d4c8c7e05151da" +dependencies = [ + "Inflector", + "base64 0.12.3", + "bincode", + "bs58", + "lazy_static", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-runtime", + "solana-sdk", + "solana-vote-program", + "spl-associated-token-account", + "spl-memo", + "spl-token", + "thiserror", +] + +[[package]] +name = "solana-version" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "797f12524e5ea25d18f345cd4c34a4839dea83e979a9ccb43a02b486ba9d913c" +dependencies = [ + "log", + "rustc_version", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-sdk", +] + +[[package]] +name = "solana-vote-program" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8583f429404acb3ab8eb255cd9cb1c65496dda7a8be3d73058d63b7d26cc1c3d" dependencies = [ "bincode", "log", @@ -2692,9 +3264,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debbc13545a1d972955a4fd3014e7c9d6d81da16c3626ee5f64bf3aa619548f8" +checksum = "7c1c5bdfa63c68d848d95024c7f4335bae4b1917f7df2e48e2d945f4664a8b45" dependencies = [ "byteorder", "combine", @@ -2703,6 +3275,7 @@ dependencies = [ "libc", "log", "rand 0.7.3", + "rustc-demangle", "scroll", "thiserror", "time", @@ -2714,6 +3287,16 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spl-associated-token-account" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4adc47eebe5d2b662cbaaba1843719c28a67e5ec5d0460bc3ca60900a51f74e2" +dependencies = [ + "solana-program", + "spl-token", +] + [[package]] name = "spl-auction" version = "0.0.1" @@ -2728,6 +3311,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-auction-test-client" +version = "0.1.0" +dependencies = [ + "bincode", + "borsh", + "clap", + "rand 0.8.3", + "solana-clap-utils", + "solana-cli-config", + "solana-client", + "solana-program", + "solana-sdk", + "spl-auction", + "spl-token", +] + +[[package]] +name = "spl-memo" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" +dependencies = [ + "solana-program", +] + [[package]] name = "spl-metaplex" version = "0.0.1" @@ -2744,6 +3353,29 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-metaplex-test-client" +version = "0.1.0" +dependencies = [ + "arrayref", + "bincode", + "borsh", + "clap", + "serde", + "serde_derive", + "serde_json", + "solana-clap-utils", + "solana-cli-config", + "solana-client", + "solana-program", + "solana-sdk", + "spl-auction", + "spl-metaplex", + "spl-token", + "spl-token-metadata", + "spl-token-vault", +] + [[package]] name = "spl-token" version = "3.1.1" @@ -2770,6 +3402,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-token-metadata-test-client" +version = "0.1.0" +dependencies = [ + "bincode", + "borsh", + "clap", + "solana-clap-utils", + "solana-cli-config", + "solana-client", + "solana-program", + "solana-sdk", + "spl-token", + "spl-token-metadata", +] + [[package]] name = "spl-token-vault" version = "0.0.1" @@ -2782,6 +3430,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-token-vault-test-client" +version = "0.1.0" +dependencies = [ + "bincode", + "borsh", + "clap", + "solana-clap-utils", + "solana-cli-config", + "solana-client", + "solana-program", + "solana-sdk", + "spl-token", + "spl-token-vault", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2794,6 +3458,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "subtle" version = "1.0.0" @@ -2912,6 +3582,34 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.25" @@ -2943,6 +3641,24 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tiny-bip39" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9e44c4759bae7f1032e286a7ef990bd9ed23fe831b7eeba0beb97484c2e59b8" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.5", + "thiserror", + "unicode-normalization", + "zeroize", +] + [[package]] name = "tinyvec" version = "1.2.0" @@ -3264,12 +3980,38 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tungstenite" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" +dependencies = [ + "base64 0.11.0", + "byteorder", + "bytes 0.5.6", + "http", + "httparse", + "input_buffer", + "log", + "native-tls", + "rand 0.7.3", + "sha-1", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicode-bidi" version = "0.3.5" @@ -3288,6 +4030,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.1.0" @@ -3337,6 +4085,24 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.3" @@ -3550,6 +4316,15 @@ dependencies = [ "libc", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zeroize" version = "1.3.0" diff --git a/rust/metaplex/program/src/processor/redeem_bid.rs b/rust/metaplex/program/src/processor/redeem_bid.rs index 6ba90ae..b7d49d8 100644 --- a/rust/metaplex/program/src/processor/redeem_bid.rs +++ b/rust/metaplex/program/src/processor/redeem_bid.rs @@ -21,6 +21,43 @@ use { }, }; +fn set_reservation_list_wrapper<'a>( + program_id: &'a Pubkey, + master_edition_info: &AccountInfo<'a>, + reservation_list_info: &AccountInfo<'a>, + auction_manager_info: &AccountInfo<'a>, + signer_seeds: &[&[u8]], + reservations: Vec, + first_push: bool, + total_reservation_spots: u64, +) -> ProgramResult { + let total_reservation_spot_opt: Option; + + if first_push { + total_reservation_spot_opt = Some(total_reservation_spots) + } else { + total_reservation_spot_opt = None + } + invoke_signed( + &set_reservation_list( + *program_id, + *master_edition_info.key, + *reservation_list_info.key, + *auction_manager_info.key, + reservations, + total_reservation_spot_opt, + ), + &[ + master_edition_info.clone(), + reservation_list_info.clone(), + auction_manager_info.clone(), + ], + &[&signer_seeds], + )?; + + Ok(()) +} + #[allow(clippy::too_many_arguments)] pub fn reserve_list_if_needed<'a>( program_id: &'a Pubkey, @@ -42,7 +79,7 @@ pub fn reserve_list_if_needed<'a>( // but may be invocation someday. It's inefficient style but better for the interface maintenance // in the long run if we move to better storage solutions (so that this action doesnt need to change if // storage does.) - + let mut total_reservation_spots: u64 = 0; for n in 0..auction_manager.settings.winning_configs.len() { match auction.winner_at(n) { Some(address) => { @@ -55,6 +92,9 @@ pub fn reserve_list_if_needed<'a>( }) .map(|i| i.amount as u64) .sum(); + total_reservation_spots = total_reservation_spots + .checked_add(spots) + .ok_or(MetaplexError::NumericalOverflowError)?; reservations.push(Reservation { address, // Select all items in a winning config matching the same safety deposit box @@ -62,27 +102,45 @@ pub fn reserve_list_if_needed<'a>( // and then sum them to get the total spots to reserve for this winner spots_remaining: spots, total_spots: spots, - }) + }); } None => break, } } - invoke_signed( - &set_reservation_list( - *program_id, - *master_edition_info.key, - *reservation_list_info.key, - *auction_manager_info.key, - reservations, - ), - &[ - master_edition_info.clone(), - reservation_list_info.clone(), - auction_manager_info.clone(), - ], - &[&signer_seeds], - )?; + let mut first_push = true; + let mut reservation_queue: Vec = vec![]; + for reservation in reservations { + reservation_queue.push(reservation); + if reservation_queue.len().checked_rem(30) == Some(0) && reservation_queue.len() > 0 { + set_reservation_list_wrapper( + program_id, + master_edition_info, + reservation_list_info, + auction_manager_info, + signer_seeds, + reservation_queue, + first_push, + total_reservation_spots, + )?; + + first_push = false; + reservation_queue = vec![]; // start over with new list. + } + } + + if reservation_queue.len() > 0 { + set_reservation_list_wrapper( + program_id, + master_edition_info, + reservation_list_info, + auction_manager_info, + signer_seeds, + reservation_queue, + first_push, + total_reservation_spots, + )?; + } } Ok(()) diff --git a/rust/token-metadata/program/src/error.rs b/rust/token-metadata/program/src/error.rs index 936cabb..70a3574 100644 --- a/rust/token-metadata/program/src/error.rs +++ b/rust/token-metadata/program/src/error.rs @@ -266,6 +266,14 @@ pub enum MetadataError { /// Data type mismatch #[error("Data type mismatch")] DataTypeMismatch, + + /// Beyond alotted address size in reservation! + #[error("Beyond alotted address size in reservation!")] + BeyondAlottedAddressSize, + + /// The reservation has only been partially alotted + #[error("The reservation has only been partially alotted")] + ReservationNotComplete, } impl PrintProgramError for MetadataError { diff --git a/rust/token-metadata/program/src/instruction.rs b/rust/token-metadata/program/src/instruction.rs index 8a831ce..8376ef5 100644 --- a/rust/token-metadata/program/src/instruction.rs +++ b/rust/token-metadata/program/src/instruction.rs @@ -45,6 +45,8 @@ pub struct MintPrintingTokensViaTokenArgs { pub struct SetReservationListArgs { /// If set, means that no more than this number of editions can ever be minted. This is immutable. pub reservations: Vec, + /// should only be present on the very first call to set reservation list. + pub total_reservation_spots: Option, } /// Instructions supported by the Metadata program. @@ -124,6 +126,10 @@ pub enum MetadataInstruction { /// with the pda that was created by that first bidder - the token metadata can then cross reference /// these people with the list and see that bidder A gets edition #2, so on and so forth. /// + /// NOTE: If you have more than 30 addresses in a reservation list, this may be called multiple times to build up the list, + /// otherwise, it simply wont fit in one transaction. Only provide a total_reservation argument on the first call, which will + /// allocate the edition space, and in follow up calls this will specifically be unnecessary (and indeed will error.) + /// /// 0. `[writable]` Master Edition key (pda of ['metadata', program id, mint id, 'edition']) /// 1. `[writable]` PDA for ReservationList of ['metadata', program id, master edition key, 'reservation', resource-key] /// 3. `[signer]` The resource you tied the reservation list too @@ -363,6 +369,7 @@ pub fn set_reservation_list( reservation_list: Pubkey, resource: Pubkey, reservations: Vec, + total_reservation_spots: Option, ) -> Instruction { Instruction { program_id, @@ -371,9 +378,12 @@ pub fn set_reservation_list( AccountMeta::new(reservation_list, false), AccountMeta::new_readonly(resource, true), ], - data: MetadataInstruction::SetReservationList(SetReservationListArgs { reservations }) - .try_to_vec() - .unwrap(), + data: MetadataInstruction::SetReservationList(SetReservationListArgs { + reservations, + total_reservation_spots, + }) + .try_to_vec() + .unwrap(), } } diff --git a/rust/token-metadata/program/src/processor.rs b/rust/token-metadata/program/src/processor.rs index 7cc3cb0..045f4c1 100644 --- a/rust/token-metadata/program/src/processor.rs +++ b/rust/token-metadata/program/src/processor.rs @@ -70,7 +70,12 @@ pub fn process_instruction( } MetadataInstruction::SetReservationList(args) => { msg!("Instruction: Set Reservation List"); - process_set_reservation_list(program_id, accounts, args.reservations) + process_set_reservation_list( + program_id, + accounts, + args.reservations, + args.total_reservation_spots, + ) } MetadataInstruction::CreateReservationList => { msg!("Instruction: Create Reservation List"); @@ -541,6 +546,7 @@ pub fn process_set_reservation_list( program_id: &Pubkey, accounts: &[AccountInfo], reservations: Vec, + total_reservation_spots: Option, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); @@ -558,10 +564,6 @@ pub fn process_set_reservation_list( return Err(MetadataError::ReservationDoesNotExist.into()); } - if reservations.len() > MAX_RESERVATIONS { - return Err(MetadataError::BeyondMaxAddressSize.into()); - } - assert_derivation( program_id, reservation_list_info, @@ -576,12 +578,12 @@ pub fn process_set_reservation_list( let mut reservation_list = get_reservation_list(reservation_list_info)?; - if reservation_list.supply_snapshot().is_some() { + if reservation_list.supply_snapshot().is_some() && total_reservation_spots.is_some() { return Err(MetadataError::ReservationAlreadyMade.into()); } - let mut total_len: u64 = 0; - let mut total_len_check: u64 = 0; + let mut total_len: u64 = reservation_list.total_reservation_spots(); + let mut total_len_check: u64 = reservation_list.total_reservation_spots(); for reservation in &reservations { total_len = total_len @@ -601,23 +603,34 @@ pub fn process_set_reservation_list( return Err(MetadataError::SpotMismatch.into()); } - reservation_list.set_supply_snapshot(Some(master_edition.supply)); - reservation_list.set_reservations(reservations); - msg!("Master edition {:?}", master_edition); - msg!("Total new spots {:?}", total_len); - master_edition.supply = master_edition - .supply - .checked_add(total_len as u64) - .ok_or(MetadataError::NumericalOverflowError)?; + reservation_list.add_reservations(reservations); - if let Some(max_supply) = master_edition.max_supply { - if master_edition.supply > max_supply { - return Err(MetadataError::ReservationBreachesMaximumSupply.into()); + if let Some(total) = total_reservation_spots { + msg!("Total new spots allocated: {:?}", total); + reservation_list.set_supply_snapshot(Some(master_edition.supply)); + reservation_list.set_total_reservation_spots(total); + master_edition.supply = master_edition + .supply + .checked_add(total as u64) + .ok_or(MetadataError::NumericalOverflowError)?; + + if let Some(max_supply) = master_edition.max_supply { + if master_edition.supply > max_supply { + return Err(MetadataError::ReservationBreachesMaximumSupply.into()); + } } + master_edition.serialize(&mut *master_edition_info.data.borrow_mut())?; + } + + if total_len > reservation_list.total_reservation_spots() { + return Err(MetadataError::BeyondAlottedAddressSize.into()); + }; + + if reservation_list.reservations().len() > MAX_RESERVATIONS { + return Err(MetadataError::BeyondMaxAddressSize.into()); } reservation_list.save(reservation_list_info)?; - master_edition.serialize(&mut *master_edition_info.data.borrow_mut())?; Ok(()) } diff --git a/rust/token-metadata/program/src/state.rs b/rust/token-metadata/program/src/state.rs index a5515db..5315aff 100644 --- a/rust/token-metadata/program/src/state.rs +++ b/rust/token-metadata/program/src/state.rs @@ -46,7 +46,7 @@ pub const MAX_RESERVATIONS: usize = 200; pub const MAX_RESERVATION_LIST_V1_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 34 + 100; // can hold up to 200 keys per reservation, note: the extra 8 is for number of elements in the vec -pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 100; +pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 8 + 92; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)] @@ -170,9 +170,12 @@ pub trait ReservationList { fn master_edition(&self) -> Pubkey; fn supply_snapshot(&self) -> Option; fn reservations(&self) -> Vec; + fn total_reservation_spots(&self) -> u64; fn set_master_edition(&mut self, key: Pubkey); fn set_supply_snapshot(&mut self, supply: Option); fn set_reservations(&mut self, reservations: Vec); + fn add_reservations(&mut self, reservations: Vec); + fn set_total_reservation_spots(&mut self, total_reservation_spots: u64); fn save(&self, account: &AccountInfo) -> ProgramResult; } @@ -199,6 +202,8 @@ pub struct ReservationListV2 { /// What supply counter was on master_edition when this reservation was created. pub supply_snapshot: Option, pub reservations: Vec, + /// How many reservations there are going to be, given on first set_reservation call + pub total_reservation_spots: u64, } impl ReservationList for ReservationListV2 { @@ -222,6 +227,10 @@ impl ReservationList for ReservationListV2 { self.supply_snapshot = supply; } + fn add_reservations(&mut self, mut reservations: Vec) { + self.reservations.append(&mut reservations) + } + fn set_reservations(&mut self, reservations: Vec) { self.reservations = reservations } @@ -230,6 +239,14 @@ impl ReservationList for ReservationListV2 { self.serialize(&mut *account.data.borrow_mut())?; Ok(()) } + + fn total_reservation_spots(&self) -> u64 { + self.total_reservation_spots + } + + fn set_total_reservation_spots(&mut self, total_reservation_spots: u64) { + self.total_reservation_spots = total_reservation_spots; + } } impl ReservationListV2 { @@ -293,7 +310,7 @@ impl ReservationList for ReservationListV1 { self.supply_snapshot = supply; } - fn set_reservations(&mut self, reservations: Vec) { + fn add_reservations(&mut self, reservations: Vec) { self.reservations = reservations .iter() .map(|r| ReservationV1 { @@ -304,10 +321,20 @@ impl ReservationList for ReservationListV1 { .collect(); } + fn set_reservations(&mut self, reservations: Vec) { + self.add_reservations(reservations); + } + fn save(&self, account: &AccountInfo) -> ProgramResult { self.serialize(&mut *account.data.borrow_mut())?; Ok(()) } + + fn total_reservation_spots(&self) -> u64 { + self.reservations.len() as u64 + } + + fn set_total_reservation_spots(&mut self, _: u64) {} } impl ReservationListV1 { diff --git a/rust/token-metadata/test/src/main.rs b/rust/token-metadata/test/src/main.rs index 23224e8..9574d38 100644 --- a/rust/token-metadata/test/src/main.rs +++ b/rust/token-metadata/test/src/main.rs @@ -5,7 +5,9 @@ use { input_validators::{is_url, is_valid_pubkey, is_valid_signer}, }, solana_client::rpc_client::RpcClient, - solana_program::{borsh::try_from_slice_unchecked, program_pack::Pack}, + solana_program::{ + account_info::AccountInfo, borsh::try_from_slice_unchecked, program_pack::Pack, + }, solana_sdk::{ pubkey::Pubkey, signature::{read_keypair_file, Keypair, Signer}, @@ -22,7 +24,9 @@ use { mint_new_edition_from_master_edition_via_token, mint_printing_tokens, update_metadata_accounts, }, - state::{Data, Edition, Key, MasterEdition, Metadata, EDITION, PREFIX}, + state::{ + get_reservation_list, Data, Edition, Key, MasterEdition, Metadata, EDITION, PREFIX, + }, }, std::str::FromStr, }; @@ -79,6 +83,24 @@ fn mint_coins(app_matches: &ArgMatches, payer: Keypair, client: RpcClient) { println!("Minted {:?} tokens to {:?}.", amount, destination_key); } +fn show_reservation_list(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) { + let key = pubkey_of(app_matches, "key").unwrap(); + let mut res_data = client.get_account(&key).unwrap(); + let mut lamports = 0; + let account_info = AccountInfo::new( + &key, + false, + false, + &mut lamports, + &mut res_data.data, + &res_data.owner, + false, + 0, + ); + + let res_list = get_reservation_list(&account_info).unwrap(); + println!("Res list {:?}", res_list.reservations()); +} fn show(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) { let program_key = spl_token_metadata::id(); @@ -735,6 +757,18 @@ fn main() { .takes_value(true) .help("Metadata mint"), ) + ).subcommand( + SubCommand::with_name("show_reservation_list") + .about("Show Reservation List") + .arg( + Arg::with_name("key") + .long("key") + .value_name("KEY") + .required(true) + .validator(is_valid_pubkey) + .takes_value(true) + .help("Account key of reservation list"), + ) ) .subcommand( SubCommand::with_name("create_master_edition") @@ -843,6 +877,9 @@ fn main() { ("show", Some(arg_matches)) => { show(arg_matches, payer, client); } + ("show_reservation_list", Some(arg_matches)) => { + show_reservation_list(arg_matches, payer, client); + } ("mint_coins", Some(arg_matches)) => { mint_coins(arg_matches, payer, client); } From 312002e4f28c0593a08e226670db39f0229b104f Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Thu, 24 Jun 2021 18:01:03 -0700 Subject: [PATCH 2/5] Some last minute fixes for res lsit --- .../program/src/processor/redeem_bid.rs | 2 +- rust/token-metadata/program/src/processor.rs | 43 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/rust/metaplex/program/src/processor/redeem_bid.rs b/rust/metaplex/program/src/processor/redeem_bid.rs index b7d49d8..9dfb508 100644 --- a/rust/metaplex/program/src/processor/redeem_bid.rs +++ b/rust/metaplex/program/src/processor/redeem_bid.rs @@ -112,7 +112,7 @@ pub fn reserve_list_if_needed<'a>( let mut reservation_queue: Vec = vec![]; for reservation in reservations { reservation_queue.push(reservation); - if reservation_queue.len().checked_rem(30) == Some(0) && reservation_queue.len() > 0 { + if reservation_queue.len().checked_rem(20) == Some(0) && reservation_queue.len() > 0 { set_reservation_list_wrapper( program_id, master_edition_info, diff --git a/rust/token-metadata/program/src/processor.rs b/rust/token-metadata/program/src/processor.rs index 045f4c1..15a2017 100644 --- a/rust/token-metadata/program/src/processor.rs +++ b/rust/token-metadata/program/src/processor.rs @@ -582,10 +582,28 @@ pub fn process_set_reservation_list( return Err(MetadataError::ReservationAlreadyMade.into()); } - let mut total_len: u64 = reservation_list.total_reservation_spots(); - let mut total_len_check: u64 = reservation_list.total_reservation_spots(); + reservation_list.add_reservations(reservations); - for reservation in &reservations { + if let Some(total) = total_reservation_spots { + reservation_list.set_supply_snapshot(Some(master_edition.supply)); + reservation_list.set_total_reservation_spots(total); + master_edition.supply = master_edition + .supply + .checked_add(total as u64) + .ok_or(MetadataError::NumericalOverflowError)?; + + if let Some(max_supply) = master_edition.max_supply { + if master_edition.supply > max_supply { + return Err(MetadataError::ReservationBreachesMaximumSupply.into()); + } + } + master_edition.serialize(&mut *master_edition_info.data.borrow_mut())?; + } + + let mut total_len: u64 = 0; + let mut total_len_check: u64 = 0; + + for reservation in reservation_list.reservations() { total_len = total_len .checked_add(reservation.spots_remaining) .ok_or(MetadataError::NumericalOverflowError)?; @@ -603,25 +621,6 @@ pub fn process_set_reservation_list( return Err(MetadataError::SpotMismatch.into()); } - reservation_list.add_reservations(reservations); - - if let Some(total) = total_reservation_spots { - msg!("Total new spots allocated: {:?}", total); - reservation_list.set_supply_snapshot(Some(master_edition.supply)); - reservation_list.set_total_reservation_spots(total); - master_edition.supply = master_edition - .supply - .checked_add(total as u64) - .ok_or(MetadataError::NumericalOverflowError)?; - - if let Some(max_supply) = master_edition.max_supply { - if master_edition.supply > max_supply { - return Err(MetadataError::ReservationBreachesMaximumSupply.into()); - } - } - master_edition.serialize(&mut *master_edition_info.data.borrow_mut())?; - } - if total_len > reservation_list.total_reservation_spots() { return Err(MetadataError::BeyondAlottedAddressSize.into()); }; From 566c52ac401951e7633f0621c8327b7004741631 Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Thu, 24 Jun 2021 20:40:20 -0500 Subject: [PATCH 3/5] Add current spots as a way to cut down on cpu time (#76) --- rust/token-metadata/program/src/processor.rs | 35 ++++++++++---------- rust/token-metadata/program/src/state.rs | 22 ++++++++++-- rust/token-metadata/test/src/main.rs | 4 +++ 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/rust/token-metadata/program/src/processor.rs b/rust/token-metadata/program/src/processor.rs index 15a2017..b4a1369 100644 --- a/rust/token-metadata/program/src/processor.rs +++ b/rust/token-metadata/program/src/processor.rs @@ -582,6 +582,24 @@ pub fn process_set_reservation_list( return Err(MetadataError::ReservationAlreadyMade.into()); } + let mut total_len: u64 = reservation_list.current_reservation_spots(); + let mut total_len_check: u64 = reservation_list.current_reservation_spots(); + + for reservation in &reservations { + total_len = total_len + .checked_add(reservation.spots_remaining) + .ok_or(MetadataError::NumericalOverflowError)?; + total_len_check = total_len_check + .checked_add(reservation.total_spots) + .ok_or(MetadataError::NumericalOverflowError)?; + if reservation.spots_remaining != reservation.total_spots { + return Err( + MetadataError::ReservationSpotsRemainingShouldMatchTotalSpotsAtStart.into(), + ); + } + } + reservation_list.set_current_reservation_spots(total_len); + reservation_list.add_reservations(reservations); if let Some(total) = total_reservation_spots { @@ -600,23 +618,6 @@ pub fn process_set_reservation_list( master_edition.serialize(&mut *master_edition_info.data.borrow_mut())?; } - let mut total_len: u64 = 0; - let mut total_len_check: u64 = 0; - - for reservation in reservation_list.reservations() { - total_len = total_len - .checked_add(reservation.spots_remaining) - .ok_or(MetadataError::NumericalOverflowError)?; - total_len_check = total_len_check - .checked_add(reservation.total_spots) - .ok_or(MetadataError::NumericalOverflowError)?; - if reservation.spots_remaining != reservation.total_spots { - return Err( - MetadataError::ReservationSpotsRemainingShouldMatchTotalSpotsAtStart.into(), - ); - } - } - if total_len_check != total_len { return Err(MetadataError::SpotMismatch.into()); } diff --git a/rust/token-metadata/program/src/state.rs b/rust/token-metadata/program/src/state.rs index 5315aff..7ba6cc6 100644 --- a/rust/token-metadata/program/src/state.rs +++ b/rust/token-metadata/program/src/state.rs @@ -46,7 +46,7 @@ pub const MAX_RESERVATIONS: usize = 200; pub const MAX_RESERVATION_LIST_V1_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 34 + 100; // can hold up to 200 keys per reservation, note: the extra 8 is for number of elements in the vec -pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 8 + 92; +pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 8 + 8 + 84; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)] @@ -171,11 +171,13 @@ pub trait ReservationList { fn supply_snapshot(&self) -> Option; fn reservations(&self) -> Vec; fn total_reservation_spots(&self) -> u64; + fn current_reservation_spots(&self) -> u64; fn set_master_edition(&mut self, key: Pubkey); fn set_supply_snapshot(&mut self, supply: Option); fn set_reservations(&mut self, reservations: Vec); fn add_reservations(&mut self, reservations: Vec); fn set_total_reservation_spots(&mut self, total_reservation_spots: u64); + fn set_current_reservation_spots(&mut self, current_reservation_spots: u64); fn save(&self, account: &AccountInfo) -> ProgramResult; } @@ -204,6 +206,8 @@ pub struct ReservationListV2 { pub reservations: Vec, /// How many reservations there are going to be, given on first set_reservation call pub total_reservation_spots: u64, + /// Cached count of reservation spots in the reservation vec to save on CPU. + pub current_reservation_spots: u64, } impl ReservationList for ReservationListV2 { @@ -247,6 +251,14 @@ impl ReservationList for ReservationListV2 { fn set_total_reservation_spots(&mut self, total_reservation_spots: u64) { self.total_reservation_spots = total_reservation_spots; } + + fn current_reservation_spots(&self) -> u64 { + self.current_reservation_spots + } + + fn set_current_reservation_spots(&mut self, current_reservation_spots: u64) { + self.current_reservation_spots = current_reservation_spots; + } } impl ReservationListV2 { @@ -331,10 +343,16 @@ impl ReservationList for ReservationListV1 { } fn total_reservation_spots(&self) -> u64 { - self.reservations.len() as u64 + self.reservations.iter().map(|r| r.total_spots as u64).sum() } fn set_total_reservation_spots(&mut self, _: u64) {} + + fn current_reservation_spots(&self) -> u64 { + self.reservations.iter().map(|r| r.total_spots as u64).sum() + } + + fn set_current_reservation_spots(&mut self, _: u64) {} } impl ReservationListV1 { diff --git a/rust/token-metadata/test/src/main.rs b/rust/token-metadata/test/src/main.rs index 9574d38..670c11f 100644 --- a/rust/token-metadata/test/src/main.rs +++ b/rust/token-metadata/test/src/main.rs @@ -100,6 +100,10 @@ fn show_reservation_list(app_matches: &ArgMatches, _payer: Keypair, client: RpcC let res_list = get_reservation_list(&account_info).unwrap(); println!("Res list {:?}", res_list.reservations()); + println!( + "current res spots: {:?}", + res_list.current_reservation_spots() + ); } fn show(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) { From 7bfbf0f0af87e4593403dd8466a54b5cdb2ba23c Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Sun, 27 Jun 2021 14:17:28 -0500 Subject: [PATCH 4/5] I feel really guilty about this, but this hack was required to scale to 200 reservations in the current reservation system. We will soon deprecate in favor of an authority based system, but didnt want to leave people behind in the mean time. (#80) --- .../program/src/processor/redeem_bid.rs | 139 ++++++++---------- rust/token-metadata/program/src/error.rs | 4 + .../token-metadata/program/src/instruction.rs | 15 +- rust/token-metadata/program/src/processor.rs | 6 +- rust/token-metadata/program/src/state.rs | 71 +++++++-- rust/token-metadata/program/src/utils.rs | 17 ++- rust/token-metadata/test/src/main.rs | 2 + 7 files changed, 164 insertions(+), 90 deletions(-) diff --git a/rust/metaplex/program/src/processor/redeem_bid.rs b/rust/metaplex/program/src/processor/redeem_bid.rs index 9dfb508..832e56b 100644 --- a/rust/metaplex/program/src/processor/redeem_bid.rs +++ b/rust/metaplex/program/src/processor/redeem_bid.rs @@ -28,16 +28,10 @@ fn set_reservation_list_wrapper<'a>( auction_manager_info: &AccountInfo<'a>, signer_seeds: &[&[u8]], reservations: Vec, - first_push: bool, - total_reservation_spots: u64, + total_reservation_spots: Option, + offset: u64, + total_spot_offset: u64, ) -> ProgramResult { - let total_reservation_spot_opt: Option; - - if first_push { - total_reservation_spot_opt = Some(total_reservation_spots) - } else { - total_reservation_spot_opt = None - } invoke_signed( &set_reservation_list( *program_id, @@ -45,7 +39,9 @@ fn set_reservation_list_wrapper<'a>( *reservation_list_info.key, *auction_manager_info.key, reservations, - total_reservation_spot_opt, + total_reservation_spots, + offset, + total_spot_offset, ), &[ master_edition_info.clone(), @@ -58,12 +54,27 @@ fn set_reservation_list_wrapper<'a>( Ok(()) } +pub fn calc_spots( + winning_config_item: &WinningConfigItem, + auction_manager: &AuctionManager, + n: usize, +) -> u64 { + auction_manager.settings.winning_configs[n] + .items + .iter() + .filter(|i| i.safety_deposit_box_index == winning_config_item.safety_deposit_box_index) + .map(|i| i.amount as u64) + .sum() +} + #[allow(clippy::too_many_arguments)] pub fn reserve_list_if_needed<'a>( program_id: &'a Pubkey, auction_manager: &AuctionManager, auction: &AuctionData, winning_config_item: &WinningConfigItem, + winning_index: usize, + bidder_info: &AccountInfo<'a>, master_edition_info: &AccountInfo<'a>, reservation_list_info: &AccountInfo<'a>, auction_manager_info: &AccountInfo<'a>, @@ -71,78 +82,56 @@ pub fn reserve_list_if_needed<'a>( ) -> ProgramResult { let reservation_list = get_reservation_list(reservation_list_info)?; - if reservation_list.supply_snapshot().is_none() { - let mut reservations: Vec = vec![]; + let total_reservation_spot_opt: Option; - // Auction specifically does not expose internal state workings as it may change someday, - // but it does expose a point get-winner-at-index method. Right now this is just array access - // but may be invocation someday. It's inefficient style but better for the interface maintenance - // in the long run if we move to better storage solutions (so that this action doesnt need to change if - // storage does.) - let mut total_reservation_spots: u64 = 0; - for n in 0..auction_manager.settings.winning_configs.len() { - match auction.winner_at(n) { - Some(address) => { - let spots: u64 = auction_manager.settings.winning_configs[n] - .items - .iter() - .filter(|i| { - i.safety_deposit_box_index - == winning_config_item.safety_deposit_box_index - }) - .map(|i| i.amount as u64) - .sum(); - total_reservation_spots = total_reservation_spots + // Auction specifically does not expose internal state workings as it may change someday, + // but it does expose a point get-winner-at-index method. Right now this is just array access + // but may be invocation someday. It's inefficient style but better for the interface maintenance + // in the long run if we move to better storage solutions (so that this action doesnt need to change if + // storage does.) + + let mut total_reservation_spots: u64 = 0; + let mut total_spot_offset: u64 = 0; + for n in 0..auction_manager.settings.winning_configs.len() { + match auction.winner_at(n) { + Some(_) => { + let spots: u64 = calc_spots(winning_config_item, auction_manager, n); + total_reservation_spots = total_reservation_spots + .checked_add(spots) + .ok_or(MetaplexError::NumericalOverflowError)?; + if n < winning_index { + total_spot_offset = total_spot_offset .checked_add(spots) .ok_or(MetaplexError::NumericalOverflowError)?; - reservations.push(Reservation { - address, - // Select all items in a winning config matching the same safety deposit box - // as the one being redeemed here (likely only one) - // and then sum them to get the total spots to reserve for this winner - spots_remaining: spots, - total_spots: spots, - }); } - None => break, } - } - - let mut first_push = true; - let mut reservation_queue: Vec = vec![]; - for reservation in reservations { - reservation_queue.push(reservation); - if reservation_queue.len().checked_rem(20) == Some(0) && reservation_queue.len() > 0 { - set_reservation_list_wrapper( - program_id, - master_edition_info, - reservation_list_info, - auction_manager_info, - signer_seeds, - reservation_queue, - first_push, - total_reservation_spots, - )?; - - first_push = false; - reservation_queue = vec![]; // start over with new list. - } - } - - if reservation_queue.len() > 0 { - set_reservation_list_wrapper( - program_id, - master_edition_info, - reservation_list_info, - auction_manager_info, - signer_seeds, - reservation_queue, - first_push, - total_reservation_spots, - )?; + None => break, } } + if reservation_list.supply_snapshot().is_none() { + total_reservation_spot_opt = Some(total_reservation_spots) + } else { + total_reservation_spot_opt = None + } + + let my_spots: u64 = calc_spots(winning_config_item, auction_manager, winning_index); + set_reservation_list_wrapper( + program_id, + master_edition_info, + reservation_list_info, + auction_manager_info, + signer_seeds, + vec![Reservation { + address: *bidder_info.key, + spots_remaining: my_spots, + total_spots: my_spots, + }], + total_reservation_spot_opt, + winning_index as u64, + total_spot_offset, + )?; + Ok(()) } pub fn process_redeem_bid<'a>( @@ -244,6 +233,8 @@ pub fn process_redeem_bid<'a>( &auction_manager, &auction, &winning_config_item, + winning_index, + bidder_info, master_edition_info, reservation_list_info, auction_manager_info, diff --git a/rust/token-metadata/program/src/error.rs b/rust/token-metadata/program/src/error.rs index 70a3574..84fb44a 100644 --- a/rust/token-metadata/program/src/error.rs +++ b/rust/token-metadata/program/src/error.rs @@ -274,6 +274,10 @@ pub enum MetadataError { /// The reservation has only been partially alotted #[error("The reservation has only been partially alotted")] ReservationNotComplete, + + /// You cannot splice over an existing reservation! + #[error("You cannot splice over an existing reservation!")] + TriedToReplaceAnExistingReservation, } impl PrintProgramError for MetadataError { diff --git a/rust/token-metadata/program/src/instruction.rs b/rust/token-metadata/program/src/instruction.rs index 8376ef5..3405365 100644 --- a/rust/token-metadata/program/src/instruction.rs +++ b/rust/token-metadata/program/src/instruction.rs @@ -47,6 +47,13 @@ pub struct SetReservationListArgs { pub reservations: Vec, /// should only be present on the very first call to set reservation list. pub total_reservation_spots: Option, + /// Where in the reservation list you want to insert this slice of reservations + pub offset: u64, + /// What the total spot offset is in the reservation list from the beginning to your slice of reservations. + /// So if is going to be 4 total editions eventually reserved between your slice and the beginning of the array, + /// split between 2 reservation entries, the offset variable above would be "2" since you start at entry 2 in 0 indexed array + /// (first 2 taking 0 and 1) and because they each have 2 spots taken, this variable would be 4. + pub total_spot_offset: u64, } /// Instructions supported by the Metadata program. @@ -126,13 +133,13 @@ pub enum MetadataInstruction { /// with the pda that was created by that first bidder - the token metadata can then cross reference /// these people with the list and see that bidder A gets edition #2, so on and so forth. /// - /// NOTE: If you have more than 30 addresses in a reservation list, this may be called multiple times to build up the list, + /// NOTE: If you have more than 20 addresses in a reservation list, this may be called multiple times to build up the list, /// otherwise, it simply wont fit in one transaction. Only provide a total_reservation argument on the first call, which will /// allocate the edition space, and in follow up calls this will specifically be unnecessary (and indeed will error.) /// /// 0. `[writable]` Master Edition key (pda of ['metadata', program id, mint id, 'edition']) /// 1. `[writable]` PDA for ReservationList of ['metadata', program id, master edition key, 'reservation', resource-key] - /// 3. `[signer]` The resource you tied the reservation list too + /// 2. `[signer]` The resource you tied the reservation list too SetReservationList(SetReservationListArgs), /// Create an empty reservation list for a resource who can come back later as a signer and fill the reservation list @@ -370,6 +377,8 @@ pub fn set_reservation_list( resource: Pubkey, reservations: Vec, total_reservation_spots: Option, + offset: u64, + total_spot_offset: u64, ) -> Instruction { Instruction { program_id, @@ -381,6 +390,8 @@ pub fn set_reservation_list( data: MetadataInstruction::SetReservationList(SetReservationListArgs { reservations, total_reservation_spots, + offset, + total_spot_offset, }) .try_to_vec() .unwrap(), diff --git a/rust/token-metadata/program/src/processor.rs b/rust/token-metadata/program/src/processor.rs index b4a1369..6379559 100644 --- a/rust/token-metadata/program/src/processor.rs +++ b/rust/token-metadata/program/src/processor.rs @@ -75,6 +75,8 @@ pub fn process_instruction( accounts, args.reservations, args.total_reservation_spots, + args.offset, + args.total_spot_offset, ) } MetadataInstruction::CreateReservationList => { @@ -547,6 +549,8 @@ pub fn process_set_reservation_list( accounts: &[AccountInfo], reservations: Vec, total_reservation_spots: Option, + offset: u64, + total_spot_offset: u64, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); @@ -600,7 +604,7 @@ pub fn process_set_reservation_list( } reservation_list.set_current_reservation_spots(total_len); - reservation_list.add_reservations(reservations); + reservation_list.add_reservations(reservations, offset, total_spot_offset)?; if let Some(total) = total_reservation_spots { reservation_list.set_supply_snapshot(Some(master_edition.supply)); diff --git a/rust/token-metadata/program/src/state.rs b/rust/token-metadata/program/src/state.rs index 7ba6cc6..94a01c2 100644 --- a/rust/token-metadata/program/src/state.rs +++ b/rust/token-metadata/program/src/state.rs @@ -174,8 +174,13 @@ pub trait ReservationList { fn current_reservation_spots(&self) -> u64; fn set_master_edition(&mut self, key: Pubkey); fn set_supply_snapshot(&mut self, supply: Option); - fn set_reservations(&mut self, reservations: Vec); - fn add_reservations(&mut self, reservations: Vec); + fn set_reservations(&mut self, reservations: Vec) -> ProgramResult; + fn add_reservations( + &mut self, + reservations: Vec, + offset: u64, + total_spot_offset: u64, + ) -> ProgramResult; fn set_total_reservation_spots(&mut self, total_reservation_spots: u64); fn set_current_reservation_spots(&mut self, current_reservation_spots: u64); fn save(&self, account: &AccountInfo) -> ProgramResult; @@ -231,12 +236,52 @@ impl ReservationList for ReservationListV2 { self.supply_snapshot = supply; } - fn add_reservations(&mut self, mut reservations: Vec) { - self.reservations.append(&mut reservations) + fn add_reservations( + &mut self, + mut reservations: Vec, + offset: u64, + total_spot_offset: u64, + ) -> ProgramResult { + let usize_offset = offset as usize; + while self.reservations.len() < usize_offset { + self.reservations.push(Reservation { + address: solana_program::system_program::id(), + spots_remaining: 0, + total_spots: 0, + }) + } + if self.reservations.len() > usize_offset { + let removed_elements: Vec = self + .reservations + .splice( + usize_offset..usize_offset + reservations.len(), + reservations, + ) + .collect(); + let existing_res = removed_elements + .iter() + .find(|r| r.address != solana_program::system_program::id()); + if existing_res.is_some() { + return Err(MetadataError::TriedToReplaceAnExistingReservation.into()); + } + } else { + self.reservations.append(&mut reservations) + } + + if usize_offset != 0 + && self.reservations[usize_offset - 1].address == solana_program::system_program::id() + { + // This becomes an anchor then for calculations... put total spot offset in here. + self.reservations[usize_offset - 1].spots_remaining = total_spot_offset; + self.reservations[usize_offset - 1].total_spots = total_spot_offset; + } + + Ok(()) } - fn set_reservations(&mut self, reservations: Vec) { - self.reservations = reservations + fn set_reservations(&mut self, reservations: Vec) -> ProgramResult { + self.reservations = reservations; + Ok(()) } fn save(&self, account: &AccountInfo) -> ProgramResult { @@ -322,7 +367,12 @@ impl ReservationList for ReservationListV1 { self.supply_snapshot = supply; } - fn add_reservations(&mut self, reservations: Vec) { + fn add_reservations( + &mut self, + reservations: Vec, + _: u64, + _: u64, + ) -> ProgramResult { self.reservations = reservations .iter() .map(|r| ReservationV1 { @@ -331,10 +381,13 @@ impl ReservationList for ReservationListV1 { total_spots: r.total_spots as u8, }) .collect(); + + Ok(()) } - fn set_reservations(&mut self, reservations: Vec) { - self.add_reservations(reservations); + fn set_reservations(&mut self, reservations: Vec) -> ProgramResult { + self.add_reservations(reservations, 0, 0)?; + Ok(()) } fn save(&self, account: &AccountInfo) -> ProgramResult { diff --git a/rust/token-metadata/program/src/utils.rs b/rust/token-metadata/program/src/utils.rs index 0b555b9..6a36f30 100644 --- a/rust/token-metadata/program/src/utils.rs +++ b/rust/token-metadata/program/src/utils.rs @@ -424,6 +424,7 @@ pub fn mint_limited_edition<'a>( let mut reservations = reservation_list.reservations(); for i in 0..reservations.len() { let mut reservation = &mut reservations[i]; + if reservation.address == *mint_authority_info.key { offset = Some( prev_total_offsets @@ -436,13 +437,21 @@ pub fn mint_limited_edition<'a>( .checked_sub(1) .ok_or(MetadataError::NumericalOverflowError)?; - reservation_list.set_reservations(reservations); + reservation_list.set_reservations(reservations)?; reservation_list.save(account)?; break; } - prev_total_offsets = prev_total_offsets - .checked_add(reservation.total_spots) - .ok_or(MetadataError::NumericalOverflowError)?; + + if reservation.address == solana_program::system_program::id() { + // This is an anchor point in the array...it means we reset our math to + // this offset because we may be missing information in between this point and + // the points before it. + prev_total_offsets = reservation.total_spots; + } else { + prev_total_offsets = prev_total_offsets + .checked_add(reservation.total_spots) + .ok_or(MetadataError::NumericalOverflowError)?; + } } match offset { diff --git a/rust/token-metadata/test/src/main.rs b/rust/token-metadata/test/src/main.rs index 670c11f..3e23425 100644 --- a/rust/token-metadata/test/src/main.rs +++ b/rust/token-metadata/test/src/main.rs @@ -104,6 +104,8 @@ fn show_reservation_list(app_matches: &ArgMatches, _payer: Keypair, client: RpcC "current res spots: {:?}", res_list.current_reservation_spots() ); + println!("total res spots: {:?}", res_list.total_reservation_spots()); + println!("supply snapshot: {:?}", res_list.supply_snapshot()); } fn show(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) { From 3ec50ee19017365b8b2d830caff75a08e4f6bfc2 Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Sun, 27 Jun 2021 19:34:38 -0500 Subject: [PATCH 5/5] Tick Size and Gap Tick Size (#66) * Draft code for tick sizes * Hooking up front to back end, need to test it now. * Don't adjust ids. * Fully working tick size and gap tick percentage * Final fixes and validations for tick and gap * Update cargo.lock --- js/packages/common/src/actions/auction.ts | 77 +++++-- .../web/src/actions/createAuctionManager.ts | 18 +- js/packages/web/src/actions/makeAuction.ts | 29 +-- .../web/src/components/AuctionCard/index.tsx | 110 ++++++++- js/packages/web/src/contexts/meta.tsx | 211 ++++++++++++------ .../web/src/views/auctionCreate/index.tsx | 35 +-- rust/auction/program/src/errors.rs | 12 + rust/auction/program/src/processor.rs | 175 +++++++++++---- .../program/src/processor/create_auction.rs | 18 ++ .../program/src/processor/place_bid.rs | 9 +- 10 files changed, 499 insertions(+), 195 deletions(-) diff --git a/js/packages/common/src/actions/auction.ts b/js/packages/common/src/actions/auction.ts index 59b11b3..b0f6576 100644 --- a/js/packages/common/src/actions/auction.ts +++ b/js/packages/common/src/actions/auction.ts @@ -15,6 +15,7 @@ import { findProgramAddress } from '../utils'; export const AUCTION_PREFIX = 'auction'; export const METADATA = 'metadata'; export const EXTENDED = 'extended'; +export const MAX_AUCTION_DATA_EXTENDED_SIZE = 8 + 9 + 2 + 200; export enum AuctionState { Created = 0, @@ -91,6 +92,23 @@ export const decodeBidderPot = (buffer: Buffer) => { return deserializeUnchecked(AUCTION_SCHEMA, BidderPot, buffer) as BidderPot; }; +export const AuctionDataExtendedParser: AccountParser = ( + pubkey: PublicKey, + account: AccountInfo, +) => ({ + pubkey, + account, + info: decodeAuctionDataExtended(account.data), +}); + +export const decodeAuctionDataExtended = (buffer: Buffer) => { + return deserializeUnchecked( + AUCTION_SCHEMA, + AuctionDataExtended, + buffer, + ) as AuctionDataExtended; +}; + export const BidderMetadataParser: AccountParser = ( pubkey: PublicKey, account: AccountInfo, @@ -323,7 +341,24 @@ export class WinnerLimit { } } -class CreateAuctionArgs { +export interface IPartialCreateAuctionArgs { + /// How many winners are allowed for this auction. See AuctionData. + winners: WinnerLimit; + /// End time is the cut-off point that the auction is forced to end by. See AuctionData. + endAuctionAt: BN | null; + /// Gap time is how much time after the previous bid where the auction ends. See AuctionData. + auctionGap: BN | null; + /// Token mint for the SPL token used for bidding. + tokenMint: PublicKey; + + priceFloor: PriceFloor; + + tickSize: BN | null; + + gapTickSizePercentage: number | null; +} + +export class CreateAuctionArgs implements IPartialCreateAuctionArgs { instruction: number = 1; /// How many winners are allowed for this auction. See AuctionData. winners: WinnerLimit; @@ -340,6 +375,10 @@ class CreateAuctionArgs { priceFloor: PriceFloor; + tickSize: BN | null; + + gapTickSizePercentage: number | null; + constructor(args: { winners: WinnerLimit; endAuctionAt: BN | null; @@ -348,6 +387,8 @@ class CreateAuctionArgs { authority: PublicKey; resource: PublicKey; priceFloor: PriceFloor; + tickSize: BN | null; + gapTickSizePercentage: number | null; }) { this.winners = args.winners; this.endAuctionAt = args.endAuctionAt; @@ -356,6 +397,8 @@ class CreateAuctionArgs { this.authority = args.authority; this.resource = args.resource; this.priceFloor = args.priceFloor; + this.tickSize = args.tickSize; + this.gapTickSizePercentage = args.gapTickSizePercentage; } } @@ -402,6 +445,8 @@ export const AUCTION_SCHEMA = new Map([ ['authority', 'pubkey'], ['resource', 'pubkey'], ['priceFloor', PriceFloor], + ['tickSize', { kind: 'option', type: 'u64' }], + ['gapTickSizePercentage', { kind: 'option', type: 'u8' }], ], }, ], @@ -541,39 +586,20 @@ export const decodeAuctionData = (buffer: Buffer) => { }; export async function createAuction( - winners: WinnerLimit, - resource: PublicKey, - endAuctionAt: BN | null, - auctionGap: BN | null, - priceFloor: PriceFloor, - tokenMint: PublicKey, - authority: PublicKey, + settings: CreateAuctionArgs, creator: PublicKey, instructions: TransactionInstruction[], ) { const auctionProgramId = programIds().auction; - const data = Buffer.from( - serialize( - AUCTION_SCHEMA, - new CreateAuctionArgs({ - winners, - resource, - endAuctionAt, - auctionGap, - tokenMint, - authority, - priceFloor, - }), - ), - ); + const data = Buffer.from(serialize(AUCTION_SCHEMA, settings)); const auctionKey: PublicKey = ( await findProgramAddress( [ Buffer.from(AUCTION_PREFIX), auctionProgramId.toBuffer(), - resource.toBuffer(), + settings.resource.toBuffer(), ], auctionProgramId, ) @@ -591,7 +617,10 @@ export async function createAuction( isWritable: true, }, { - pubkey: await getAuctionExtended({ auctionProgramId, resource }), + pubkey: await getAuctionExtended({ + auctionProgramId, + resource: settings.resource, + }), isSigner: false, isWritable: true, }, diff --git a/js/packages/web/src/actions/createAuctionManager.ts b/js/packages/web/src/actions/createAuctionManager.ts index 2eb1a61..57d7f07 100644 --- a/js/packages/web/src/actions/createAuctionManager.ts +++ b/js/packages/web/src/actions/createAuctionManager.ts @@ -8,7 +8,6 @@ import { actions, Metadata, ParsedAccount, - WinnerLimit, MasterEdition, SequenceType, sendTransactions, @@ -20,8 +19,8 @@ import { getSafetyDepositBoxAddress, createAssociatedTokenAccountInstruction, sendTransactionWithRetry, - PriceFloor, findProgramAddress, + IPartialCreateAuctionArgs, } from '@oyster/common'; import { AccountLayout, Token } from '@solana/spl-token'; @@ -100,13 +99,10 @@ export async function createAuctionManager( ParsedAccount >, settings: AuctionManagerSettings, - winnerLimit: WinnerLimit, - endAuctionAt: BN, - auctionGap: BN, + auctionSettings: IPartialCreateAuctionArgs, safetyDepositDrafts: SafetyDepositDraft[], participationSafetyDepositDraft: SafetyDepositDraft | undefined, paymentMint: PublicKey, - priceFloor: PriceFloor, ): Promise<{ vault: PublicKey; auction: PublicKey; @@ -136,15 +132,7 @@ export async function createAuctionManager( instructions: makeAuctionInstructions, signers: makeAuctionSigners, auction, - } = await makeAuction( - wallet, - winnerLimit, - vault, - endAuctionAt, - auctionGap, - paymentMint, - priceFloor, - ); + } = await makeAuction(wallet, vault, auctionSettings); let safetyDepositConfigsWithPotentiallyUnsetTokens = await buildSafetyDepositArray( diff --git a/js/packages/web/src/actions/makeAuction.ts b/js/packages/web/src/actions/makeAuction.ts index f38e261..0bcf968 100644 --- a/js/packages/web/src/actions/makeAuction.ts +++ b/js/packages/web/src/actions/makeAuction.ts @@ -2,24 +2,19 @@ import { Keypair, PublicKey, TransactionInstruction } from '@solana/web3.js'; import { utils, actions, - WinnerLimit, - PriceFloor, findProgramAddress, + IPartialCreateAuctionArgs, + CreateAuctionArgs, } from '@oyster/common'; -import BN from 'bn.js'; import { METAPLEX_PREFIX } from '../models/metaplex'; const { AUCTION_PREFIX, createAuction } = actions; // This command makes an auction export async function makeAuction( wallet: any, - winnerLimit: WinnerLimit, vault: PublicKey, - endAuctionAt: BN, - auctionGap: BN, - paymentMint: PublicKey, - priceFloor: PriceFloor, + auctionSettings: IPartialCreateAuctionArgs, ): Promise<{ auction: PublicKey; instructions: TransactionInstruction[]; @@ -47,17 +42,13 @@ export async function makeAuction( ) )[0]; - createAuction( - winnerLimit, - vault, - endAuctionAt, - auctionGap, - priceFloor, - paymentMint, - auctionManagerKey, - wallet.publicKey, - instructions, - ); + const fullSettings = new CreateAuctionArgs({ + ...auctionSettings, + authority: auctionManagerKey, + resource: vault, + }); + + createAuction(fullSettings, wallet.publicKey, instructions); return { instructions, signers, auction: auctionKey }; } diff --git a/js/packages/web/src/components/AuctionCard/index.tsx b/js/packages/web/src/components/AuctionCard/index.tsx index 28291ba..7b96f5e 100644 --- a/js/packages/web/src/components/AuctionCard/index.tsx +++ b/js/packages/web/src/components/AuctionCard/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Col, Button, InputNumber, Spin } from 'antd'; import { MemoryRouter, Route, Redirect, Link } from 'react-router-dom'; @@ -13,6 +13,10 @@ import { formatTokenAmount, useMint, PriceFloorType, + AuctionDataExtended, + ParsedAccount, + getAuctionExtended, + programIds, } from '@oyster/common'; import { AuctionView, useUserBalance } from '../../hooks'; import { sendPlaceBid } from '../../actions/sendPlaceBid'; @@ -26,9 +30,78 @@ import BN from 'bn.js'; import { Confetti } from '../Confetti'; import { QUOTE_MINT } from '../../constants'; import { LAMPORTS_PER_SOL } from '@solana/web3.js'; +import { useMeta } from '../../contexts'; +import moment from 'moment'; const { useWallet } = contexts.Wallet; +function useGapTickCheck( + value: number | undefined, + gapTick: number | null, + gapTime: number, + auctionView: AuctionView, +): boolean { + return !!useMemo(() => { + if (gapTick && value && gapTime && !auctionView.auction.info.ended()) { + // so we have a gap tick percentage, and a gap tick time, and a value, and we're not ended - are we within gap time? + const now = moment().unix(); + const endedAt = auctionView.auction.info.endedAt; + if (endedAt) { + const ended = endedAt.toNumber(); + if (now > ended) { + const toLamportVal = value * LAMPORTS_PER_SOL; + // Ok, we are in gap time, since now is greater than ended and we're not actually an ended auction yt. + // Check that the bid is at least gapTick % bigger than the next biggest one in the stack. + for ( + let i = auctionView.auction.info.bidState.bids.length - 1; + i > -1; + i-- + ) { + const bid = auctionView.auction.info.bidState.bids[i]; + const expected = bid.amount.toNumber(); + if (expected < toLamportVal) { + const higherExpectedAmount = expected * ((100 + gapTick) / 100); + + return higherExpectedAmount > toLamportVal; + } else if (expected == toLamportVal) { + // If gap tick is set, no way you can bid in this case - you must bid higher. + return true; + } + } + return false; + } else { + return false; + } + } + return false; + } + }, [value, gapTick, gapTime, auctionView]); +} + +function useAuctionExtended( + auctionView: AuctionView, +): ParsedAccount | undefined { + const [auctionExtended, setAuctionExtended] = + useState>(); + const { auctionDataExtended } = useMeta(); + + useMemo(() => { + const fn = async () => { + if (!auctionExtended) { + const PROGRAM_IDS = programIds(); + const extendedKey = await getAuctionExtended({ + auctionProgramId: PROGRAM_IDS.auction, + resource: auctionView.vault.pubkey, + }); + const extendedValue = auctionDataExtended[extendedKey.toBase58()]; + if (extendedValue) setAuctionExtended(extendedValue); + } + }; + fn(); + }, [auctionDataExtended, auctionExtended, setAuctionExtended]); + + return auctionExtended; +} export const AuctionCard = ({ auctionView, style, @@ -74,9 +147,21 @@ export const AuctionCard = ({ winnerIndex, auctionView, ); + const auctionExtended = useAuctionExtended(auctionView); const eligibleForAnything = winnerIndex !== null || eligibleForOpenEdition; const gapTime = (auctionView.auction.info.auctionGap?.toNumber() || 0) / 60; + const gapTick = auctionExtended + ? auctionExtended.info.gapTickSizePercentage + : 0; + const tickSize = auctionExtended ? auctionExtended.info.tickSize : 0; + const tickSizeInvalid = !!( + tickSize && + value && + (value * LAMPORTS_PER_SOL) % tickSize.toNumber() != 0 + ); + + const gapBidInvalid = useGapTickCheck(value, gapTick, gapTime, auctionView); return (
@@ -270,13 +355,32 @@ export const AuctionCard = ({ }} > Bids placed in the last {gapTime} minutes will extend - bidding for another {gapTime} minutes. + bidding for another {gapTime} minutes beyond the point in + time that bid was made.{' '} + {gapTick && ( + + Additionally, once the official auction end time has + passed, only bids {gapTick}% larger than an existing + bid will be accepted. + + )}
)}

+ {tickSizeInvalid && tickSize && ( + + Tick size is ◎{tickSize.toNumber() / LAMPORTS_PER_SOL}. + + )} + {gapBidInvalid && ( + + Your bid needs to be at least {gapTick}% larger than an + existing bid during gap periods to be eligible. + + )}
>; auctionManagersByAuction: Record>; auctions: Record>; + auctionDataExtended: Record>; vaults: Record>; store: ParsedAccount | null; bidderMetadataByAuctionAndBidder: Record< @@ -88,29 +92,38 @@ interface MetaState { const { MetadataKey } = actions; -type UpdateStateValueFunc = (prop: keyof MetaState, key: string, value: any) => void; +type UpdateStateValueFunc = ( + prop: keyof MetaState, + key: string, + value: any, +) => void; export interface MetaContextState extends MetaState { isLoading: boolean; } -const isMetadataPartOfStore = (m: ParsedAccount , store: ParsedAccount | null, whitelistedCreatorsByCreator: Record< - string, - ParsedAccount ->) => { - if(!m?.info?.data?.creators) { +const isMetadataPartOfStore = ( + m: ParsedAccount, + store: ParsedAccount | null, + whitelistedCreatorsByCreator: Record< + string, + ParsedAccount + >, +) => { + if (!m?.info?.data?.creators) { return false; } - return m.info.data.creators.findIndex( + return ( + m.info.data.creators.findIndex( c => c.verified && store && store.info && (store.info.public || - whitelistedCreatorsByCreator[c.address.toBase58()]?.info - ?.activated), - ) >= 0; -} + whitelistedCreatorsByCreator[c.address.toBase58()]?.info?.activated), + ) >= 0 + ); +}; const MetaContext = React.createContext({ metadata: [], @@ -122,6 +135,7 @@ const MetaContext = React.createContext({ editions: {}, auctionManagersByAuction: {}, auctions: {}, + auctionDataExtended: {}, vaults: {}, store: null, isLoading: false, @@ -141,13 +155,20 @@ export function MetaProvider({ children = null as any }) { metadata: [] as Array>, metadataByMint: {} as Record>, masterEditions: {} as Record>, - masterEditionsByPrintingMint: {} as Record>, - masterEditionsByOneTimeAuthMint: {} as Record>, + masterEditionsByPrintingMint: {} as Record< + string, + ParsedAccount + >, + masterEditionsByOneTimeAuthMint: {} as Record< + string, + ParsedAccount + >, metadataByMasterEdition: {} as any, editions: {}, auctionManagersByAuction: {}, bidRedemptions: {}, auctions: {}, + auctionDataExtended: {}, vaults: {}, payoutTickets: {}, store: null as ParsedAccount | null, @@ -155,7 +176,7 @@ export function MetaProvider({ children = null as any }) { bidderMetadataByAuctionAndBidder: {}, bidderPotsByAuctionAndBidder: {}, safetyDepositBoxesByVaultAndIndex: {}, - }) + }); const [isLoading, setIsLoading] = useState(true); @@ -163,11 +184,11 @@ export function MetaProvider({ children = null as any }) { async metadataByMint => { try { const m = await queryExtendedMetadata(connection, metadataByMint); - setState((current) => ({ + setState(current => ({ ...current, metadata: m.metadata, metadataByMint: m.mintToMetadata, - })) + })); } catch (er) { console.error(er); } @@ -203,6 +224,7 @@ export function MetaProvider({ children = null as any }) { auctionManagersByAuction: {}, bidRedemptions: {}, auctions: {}, + auctionDataExtended: {}, vaults: {}, payoutTickets: {}, store: null, @@ -215,11 +237,11 @@ export function MetaProvider({ children = null as any }) { const updateTemp = (prop: keyof MetaState, key: string, value: any) => { if (prop === 'store') { tempCache[prop] = value; - } else if(tempCache[prop]) { + } else if (tempCache[prop]) { const bucket = tempCache[prop] as any; bucket[key] = value as any; } - } + }; for (let i = 0; i < accounts.length; i++) { let account = accounts[i]; @@ -227,18 +249,25 @@ export function MetaProvider({ children = null as any }) { processAuctions(account, updateTemp); processMetaData(account, updateTemp); - await processMetaplexAccounts( - account, - updateTemp, - ); + await processMetaplexAccounts(account, updateTemp); } - const values = Object.values(tempCache.metadataByMint) as ParsedAccount[]; + const values = Object.values( + tempCache.metadataByMint, + ) as ParsedAccount[]; for (let i = 0; i < values.length; i++) { const metadata = values[i]; - if(isMetadataPartOfStore(metadata, tempCache.store, tempCache.whitelistedCreatorsByCreator)) { + if ( + isMetadataPartOfStore( + metadata, + tempCache.store, + tempCache.whitelistedCreatorsByCreator, + ) + ) { await metadata.info.init(); - tempCache.metadataByMasterEdition[metadata.info?.masterEdition?.toBase58() || ''] = metadata; + tempCache.metadataByMasterEdition[ + metadata.info?.masterEdition?.toBase58() || '' + ] = metadata; } else { delete tempCache.metadataByMint[metadata.info.mint.toBase58() || '']; } @@ -248,7 +277,7 @@ export function MetaProvider({ children = null as any }) { tempCache.metadata = values; setState({ ...tempCache, - }) + }); setIsLoading(false); console.log('------->set finished'); @@ -259,36 +288,34 @@ export function MetaProvider({ children = null as any }) { return () => { dispose(); }; - }, [ - connection, - setState, - updateMints, - env, - ]); + }, [connection, setState, updateMints, env]); - const updateStateValue = useMemo(() => (prop: keyof MetaState, key: string, value: any) => { - setState((current) => { - if (prop === 'store') { - return { - ...current, - [prop]: value, + const updateStateValue = useMemo( + () => (prop: keyof MetaState, key: string, value: any) => { + setState(current => { + if (prop === 'store') { + return { + ...current, + [prop]: value, + }; + } else { + return { + ...current, + [prop]: { + ...current[prop], + [key]: value, + }, + }; } - } else { - return ({ - ...current, - [prop]: { - ...current[prop], - [key]: value - } - }); - } - }); - }, [setState]); + }); + }, + [setState], + ); const store = state.store; const whitelistedCreatorsByCreator = state.whitelistedCreatorsByCreator; useEffect(() => { - if(isLoading) { + if (isLoading) { return; } @@ -341,9 +368,16 @@ export function MetaProvider({ children = null as any }) { updateStateValue, ); - if(result && isMetadataPartOfStore(result, store, whitelistedCreatorsByCreator)) { + if ( + result && + isMetadataPartOfStore(result, store, whitelistedCreatorsByCreator) + ) { await result.info.init(); - updateStateValue('metadataByMasterEdition', result.info.masterEdition?.toBase58() || '', result); + updateStateValue( + 'metadataByMasterEdition', + result.info.masterEdition?.toBase58() || '', + result, + ); } // TODO: BL @@ -366,7 +400,7 @@ export function MetaProvider({ children = null as any }) { pubkey, account: info.accountInfo, }, - updateStateValue + updateStateValue, ); }, ); @@ -425,9 +459,12 @@ export function MetaProvider({ children = null as any }) { masterEditions: state.masterEditions, auctionManagersByAuction: state.auctionManagersByAuction, auctions: state.auctions, + auctionDataExtended: state.auctionDataExtended, metadataByMint: state.metadataByMint, - safetyDepositBoxesByVaultAndIndex: state.safetyDepositBoxesByVaultAndIndex, - bidderMetadataByAuctionAndBidder: state.bidderMetadataByAuctionAndBidder, + safetyDepositBoxesByVaultAndIndex: + state.safetyDepositBoxesByVaultAndIndex, + bidderMetadataByAuctionAndBidder: + state.bidderMetadataByAuctionAndBidder, bidderPotsByAuctionAndBidder: state.bidderPotsByAuctionAndBidder, vaults: state.vaults, bidRedemptions: state.bidRedemptions, @@ -520,6 +557,21 @@ const processAuctions = ( // ignore errors // add type as first byte for easier deserialization } + + try { + if (a.account.data.length === MAX_AUCTION_DATA_EXTENDED_SIZE) { + const account = cache.add( + a.pubkey, + a.account, + AuctionDataExtendedParser, + false, + ) as ParsedAccount; + setter('auctionDataExtended', a.pubkey.toBase58(), account); + } + } catch { + // ignore errors + // add type as first byte for easier deserialization + } try { if (a.account.data.length === BIDDER_METADATA_LEN) { const account = cache.add( @@ -531,9 +583,10 @@ const processAuctions = ( setter( 'bidderMetadataByAuctionAndBidder', account.info.auctionPubkey.toBase58() + - '-' + - account.info.bidderPubkey.toBase58(), - account); + '-' + + account.info.bidderPubkey.toBase58(), + account, + ); } } catch { // ignore errors @@ -550,9 +603,10 @@ const processAuctions = ( setter( 'bidderPotsByAuctionAndBidder', account.info.auctionAct.toBase58() + - '-' + - account.info.bidderAct.toBase58(), - account); + '-' + + account.info.bidderAct.toBase58(), + account, + ); } } catch { // ignore errors @@ -586,7 +640,11 @@ const processMetaplexAccounts = async ( account: a.account, info: auctionManager, }; - setter('auctionManagersByAuction', auctionManager.auction.toBase58(), account); + setter( + 'auctionManagersByAuction', + auctionManager.auction.toBase58(), + account, + ); } } } else if (a.account.data[0] === MetaplexKey.BidRedemptionTicketV1) { @@ -640,7 +698,11 @@ const processMetaplexAccounts = async ( account.info.image = nameInfo.image; account.info.twitter = nameInfo.twitter; } - setter('whitelistedCreatorsByCreator', whitelistedCreator.address.toBase58(), account); + setter( + 'whitelistedCreatorsByCreator', + whitelistedCreator.address.toBase58(), + account, + ); } } } catch { @@ -653,7 +715,8 @@ const processMetaData = ( meta: PublicKeyAndAccount, setter: UpdateStateValueFunc, ) => { - if (meta.account.owner.toBase58() !== programIds().metadata.toBase58()) return; + if (meta.account.owner.toBase58() !== programIds().metadata.toBase58()) + return; try { if (meta.account.data[0] === MetadataKey.MetadataV1) { @@ -687,8 +750,16 @@ const processMetaData = ( info: masterEdition, }; setter('masterEditions', meta.pubkey.toBase58(), account); - setter('masterEditionsByPrintingMint', masterEdition.printingMint.toBase58(), account); - setter('masterEditionsByOneTimeAuthMint', masterEdition.oneTimePrintingAuthorizationMint.toBase58(), account); + setter( + 'masterEditionsByPrintingMint', + masterEdition.printingMint.toBase58(), + account, + ); + setter( + 'masterEditionsByOneTimeAuthMint', + masterEdition.oneTimePrintingAuthorizationMint.toBase58(), + account, + ); } } catch { // ignore errors @@ -712,7 +783,8 @@ const processVaultData = ( setter( 'safetyDepositBoxesByVaultAndIndex', safetyDeposit.vault.toBase58() + '-' + safetyDeposit.order, - account); + account, + ); } else if (a.account.data[0] === VaultKey.VaultV1) { const vault = decodeVault(a.account.data); const account: ParsedAccount = { @@ -721,10 +793,7 @@ const processVaultData = ( info: vault, }; - setter( - 'vaults', - a.pubkey.toBase58(), - account); + setter('vaults', a.pubkey.toBase58(), account); } } catch { // ignore errors diff --git a/js/packages/web/src/views/auctionCreate/index.tsx b/js/packages/web/src/views/auctionCreate/index.tsx index b88e9ac..bf0fcee 100644 --- a/js/packages/web/src/views/auctionCreate/index.tsx +++ b/js/packages/web/src/views/auctionCreate/index.tsx @@ -30,13 +30,9 @@ import { Creator, PriceFloor, PriceFloorType, + IPartialCreateAuctionArgs, } from '@oyster/common'; -import { - Connection, - LAMPORTS_PER_SOL, - PublicKey, - SystemProgram, -} from '@solana/web3.js'; +import { Connection, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'; import { MintLayout } from '@solana/spl-token'; import { useHistory, useParams } from 'react-router-dom'; import { capitalize } from 'lodash'; @@ -306,14 +302,29 @@ export const AuctionCreateView = () => { console.log('Tiered settings', settings); } + const auctionSettings: IPartialCreateAuctionArgs = { + winners: winnerLimit, + endAuctionAt: new BN((attributes.auctionDuration || 0) * 60), // endAuctionAt is actually auction duration, poorly named, in seconds + auctionGap: new BN((attributes.gapTime || 0) * 60), + priceFloor: new PriceFloor({ + type: attributes.priceFloor + ? PriceFloorType.Minimum + : PriceFloorType.None, + minPrice: new BN((attributes.priceFloor || 0) * LAMPORTS_PER_SOL), + }), + tokenMint: QUOTE_MINT, + gapTickSizePercentage: attributes.tickSizeEndingPhase || null, + tickSize: attributes.priceTick + ? new BN(attributes.priceTick * LAMPORTS_PER_SOL) + : null, + }; + const _auctionObj = await createAuctionManager( connection, wallet, whitelistedCreatorsByCreator, settings, - winnerLimit, - new BN((attributes.auctionDuration || 0) * 60), // endAuctionAt is actually auction duration, poorly named, in seconds - new BN((attributes.gapTime || 0) * 60), + auctionSettings, attributes.category === AuctionCategory.Open ? [] : attributes.category !== AuctionCategory.Tiered @@ -323,12 +334,6 @@ export const AuctionCreateView = () => { ? attributes.items[0] : attributes.participationNFT, QUOTE_MINT, - new PriceFloor({ - type: attributes.priceFloor - ? PriceFloorType.Minimum - : PriceFloorType.None, - minPrice: new BN((attributes.priceFloor || 0) * LAMPORTS_PER_SOL), - }), ); setAuctionObj(_auctionObj); }; diff --git a/rust/auction/program/src/errors.rs b/rust/auction/program/src/errors.rs index c4cb3ad..9152ccf 100644 --- a/rust/auction/program/src/errors.rs +++ b/rust/auction/program/src/errors.rs @@ -122,6 +122,18 @@ pub enum AuctionError { /// Data type mismatch #[error("Data type mismatch")] DataTypeMismatch, + + /// Bid must be multiple of tick size + #[error("Bid must be multiple of tick size")] + BidMustBeMultipleOfTickSize, + + /// During the gap window, gap between next lowest bid must be of a certain percentage + #[error("During the gap window, gap between next lowest bid must be of a certain percentage")] + GapBetweenBidsTooSmall, + + /// Gap tick size percentage must be between 0 and 100 + #[error("Gap tick size percentage must be between 0 and 100")] + InvalidGapTickSizePercentage, } impl PrintProgramError for AuctionError { diff --git a/rust/auction/program/src/processor.rs b/rust/auction/program/src/processor.rs index c756b36..f873762 100644 --- a/rust/auction/program/src/processor.rs +++ b/rust/auction/program/src/processor.rs @@ -134,10 +134,10 @@ impl AuctionData { (Some(end), Some(gap)) => { // Check if the bid is within the gap between the last bidder. if let Some(last) = self.last_bid { - let next_bid_time = match last.checked_add(gap) { - Some(val) => val, - None => return Err(AuctionError::NumericalOverflowError.into()), - }; + let next_bid_time = last + .checked_add(gap) + .ok_or(AuctionError::NumericalOverflowError)?; + Ok(now > end && now > next_bid_time) } else { Ok(now > end) @@ -179,6 +179,28 @@ impl AuctionData { }; self.bid_state.winner_at(idx, minimum) } + + pub fn place_bid( + &mut self, + bid: Bid, + tick_size: Option, + gap_tick_size_percentage: Option, + now: UnixTimestamp, + ) -> Result<(), ProgramError> { + let gap_val = match self.ended_at { + Some(end) => { + // We use the actual gap tick size perc if we're in gap window, + // otherwise we pass in none so the logic isnt used + if now > end { + gap_tick_size_percentage + } else { + None + } + } + None => None, + }; + self.bid_state.place_bid(bid, tick_size, gap_val) + } } /// Define valid auction state transitions. @@ -259,59 +281,120 @@ impl BidState { real_max } + fn assert_valid_tick_size_bid(bid: &Bid, tick_size: Option) -> ProgramResult { + if let Some(tick) = tick_size { + if bid.1.checked_rem(tick) != Some(0) { + msg!( + "This bid {:?} is not a multiple of tick size {:?}, throw it out.", + bid.1, + tick_size + ); + return Err(AuctionError::BidMustBeMultipleOfTickSize.into()); + } + } else { + msg!("No tick size on this auction") + } + + Ok(()) + } + + fn assert_valid_gap_insertion( + gap_tick: u8, + beaten_bid: &Bid, + beating_bid: &Bid, + ) -> ProgramResult { + // Use u128 to avoid potential overflow due to temporary mult of 100x since + // we haven't divided yet. + let mut minimum_bid_amount: u128 = (beaten_bid.1 as u128) + .checked_mul((100 + gap_tick) as u128) + .ok_or(AuctionError::NumericalOverflowError)?; + minimum_bid_amount = minimum_bid_amount + .checked_div(100u128) + .ok_or(AuctionError::NumericalOverflowError)?; + + if minimum_bid_amount > beating_bid.1 as u128 { + msg!("Rejecting inserting this bid due to gap tick size of {:?} which causes min bid of {:?} from {:?} which is the bid it is trying to beat", gap_tick, minimum_bid_amount.to_string(), beaten_bid.1); + return Err(AuctionError::GapBetweenBidsTooSmall.into()); + } + + Ok(()) + } + /// Push a new bid into the state, this succeeds only if the bid is larger than the current top /// winner stored. Crappy list information to start with. - pub fn place_bid(&mut self, bid: Bid) -> Result<(), ProgramError> { + pub fn place_bid( + &mut self, + bid: Bid, + tick_size: Option, + gap_tick_size_percentage: Option, + ) -> Result<(), ProgramError> { + msg!("Placing bid {:?}", &bid.1.to_string()); + BidState::assert_valid_tick_size_bid(&bid, tick_size)?; + match self { // In a capped auction, track the limited number of winners. - BidState::EnglishAuction { ref mut bids, max } => match bids.last() { - Some(top) => { - msg!("Looking to go over the loop"); - for i in (0..bids.len()).rev() { - msg!("Comparison of {:?} and {:?} for {:?}", bids[i].1, bid.1, i); - if bids[i].1 < bid.1 { - msg!("Ok we can do an insert"); - if i + 1 < bids.len() { - msg!("Doing a normal insert"); - bids.insert(i + 1, bid); - } else { - msg!("Doing an on the end insert"); - bids.push(bid) - } - break; - } else if bids[i].1 == bid.1 { - msg!("Ok we can do an equivalent insert"); - if i == 0 { - msg!("Doing a normal insert"); + BidState::EnglishAuction { ref mut bids, max } => { + match bids.last() { + Some(top) => { + msg!("Looking to go over the loop, but check tick size first"); + + for i in (0..bids.len()).rev() { + msg!("Comparison of {:?} and {:?} for {:?}", bids[i].1, bid.1, i); + if bids[i].1 < bid.1 { + if let Some(gap_tick) = gap_tick_size_percentage { + BidState::assert_valid_gap_insertion(gap_tick, &bids[i], &bid)? + } + + msg!("Ok we can do an insert"); + if i + 1 < bids.len() { + msg!("Doing a normal insert"); + bids.insert(i + 1, bid); + } else { + msg!("Doing an on the end insert"); + bids.push(bid) + } + break; + } else if bids[i].1 == bid.1 { + if let Some(gap_tick) = gap_tick_size_percentage { + if gap_tick > 0 { + msg!("Rejecting same-bid insert due to gap tick size of {:?}", gap_tick); + return Err(AuctionError::GapBetweenBidsTooSmall.into()); + } + } + + msg!("Ok we can do an equivalent insert"); + if i == 0 { + msg!("Doing a normal insert"); + bids.insert(0, bid); + break; + } else { + if bids[i - 1].1 != bids[i].1 { + msg!("Doing an insert just before"); + bids.insert(i, bid); + break; + } + msg!("More duplicates ahead...") + } + } else if i == 0 { + msg!("Inserting at 0"); bids.insert(0, bid); break; - } else { - if bids[i - 1].1 != bids[i].1 { - msg!("Doing an insert just before"); - bids.insert(i, bid); - break; - } - msg!("More duplicates ahead...") } - } else if i == 0 { - msg!("Inserting at 0"); - bids.insert(0, bid); - break; } - } - let max_size = BidState::max_array_size_for(*max); + let max_size = BidState::max_array_size_for(*max); - if bids.len() > max_size { - bids.remove(0); + if bids.len() > max_size { + bids.remove(0); + } + Ok(()) + } + _ => { + msg!("Pushing bid onto stack"); + bids.push(bid); + Ok(()) } - Ok(()) } - _ => { - msg!("Pushing bid onto stack"); - bids.push(bid); - Ok(()) - } - }, + } // In an open auction, bidding simply succeeds. BidState::OpenEdition { bids, max } => Ok(()), diff --git a/rust/auction/program/src/processor/create_auction.rs b/rust/auction/program/src/processor/create_auction.rs index 60ac4ac..7841e7a 100644 --- a/rust/auction/program/src/processor/create_auction.rs +++ b/rust/auction/program/src/processor/create_auction.rs @@ -40,6 +40,10 @@ pub struct CreateAuctionArgs { pub resource: Pubkey, /// Set a price floor. pub price_floor: PriceFloor, + /// Add a tick size increment + pub tick_size: Option, + /// Add a minimum percentage increase each bid must meet. + pub gap_tick_size_percentage: Option, } struct Accounts<'a, 'b: 'a> { @@ -98,6 +102,12 @@ pub fn create_auction( WinnerLimit::Unlimited(_) => BidState::new_open_edition(), }; + if let Some(gap_tick) = args.gap_tick_size_percentage { + if gap_tick > 100 { + return Err(AuctionError::InvalidGapTickSizePercentage.into()); + } + } + // Create auction account with enough space for a winner tracking. create_or_allocate_account_raw( *program_id, @@ -141,6 +151,14 @@ pub fn create_auction( ], )?; + // Configure extended + AuctionDataExtended { + total_uncancelled_bids: 0, + tick_size: args.tick_size, + gap_tick_size_percentage: args.gap_tick_size_percentage, + } + .serialize(&mut *accounts.auction_extended.data.borrow_mut())?; + // Configure Auction. AuctionData { authority: args.authority, diff --git a/rust/auction/program/src/processor/place_bid.rs b/rust/auction/program/src/processor/place_bid.rs index cdff99f..f184f3c 100644 --- a/rust/auction/program/src/processor/place_bid.rs +++ b/rust/auction/program/src/processor/place_bid.rs @@ -316,9 +316,12 @@ pub fn place_bid<'r, 'b: 'r>( // Serialize new Auction State auction.last_bid = Some(clock.unix_timestamp); - auction - .bid_state - .place_bid(Bid(*accounts.bidder.key, args.amount))?; + auction.place_bid( + Bid(*accounts.bidder.key, args.amount), + auction_extended.tick_size, + auction_extended.gap_tick_size_percentage, + clock.unix_timestamp, + )?; auction.serialize(&mut *accounts.auction.data.borrow_mut())?; // Update latest metadata with results from the bid.