state.move 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. module pyth_lazer::state;
  2. use pyth_lazer::admin::{Self, AdminCap};
  3. const SECP256K1_COMPRESSED_PUBKEY_LEN: u64 = 33;
  4. const EInvalidPubkeyLen: u64 = 1;
  5. const ESignerNotFound: u64 = 2;
  6. /// Lazer State consists of the current set of trusted signers.
  7. /// By verifying that a price update was signed by one of these public keys,
  8. /// you can validate the authenticity of a Lazer price update.
  9. ///
  10. /// The trusted signers are subject to rotations and expiry.
  11. public struct State has key, store {
  12. id: UID,
  13. trusted_signers: vector<TrustedSignerInfo>,
  14. }
  15. /// A trusted signer is comprised of a pubkey and an expiry timestamp (seconds since Unix epoch).
  16. /// A signer's signature should only be trusted up to timestamp `expires_at`.
  17. public struct TrustedSignerInfo has copy, drop, store {
  18. public_key: vector<u8>,
  19. expires_at: u64,
  20. }
  21. public(package) fun new(ctx: &mut TxContext): State {
  22. State {
  23. id: object::new(ctx),
  24. trusted_signers: vector::empty<TrustedSignerInfo>(),
  25. }
  26. }
  27. /// Get the trusted signer's public key
  28. public fun public_key(info: &TrustedSignerInfo): &vector<u8> {
  29. &info.public_key
  30. }
  31. /// Get the trusted signer's expiry timestamp (seconds since Unix epoch)
  32. public fun expires_at(info: &TrustedSignerInfo): u64 {
  33. info.expires_at
  34. }
  35. /// Get the list of trusted signers
  36. public fun get_trusted_signers(s: &State): &vector<TrustedSignerInfo> {
  37. &s.trusted_signers
  38. }
  39. /// Upsert a trusted signer's information or remove them. Can only be called by the AdminCap holder.
  40. /// - If the trusted signer pubkey already exists, the expires_at will be updated.
  41. /// - If the expired_at is set to zero, the trusted signer will be removed.
  42. /// - If the pubkey isn't found, it is added as a new trusted signer with the given expires_at.
  43. public fun update_trusted_signer(_: &AdminCap, s: &mut State, pubkey: vector<u8>, expires_at: u64) {
  44. assert!(vector::length(&pubkey) as u64 == SECP256K1_COMPRESSED_PUBKEY_LEN, EInvalidPubkeyLen);
  45. let mut maybe_idx = find_signer_index(&s.trusted_signers, &pubkey);
  46. if (expires_at == 0) {
  47. if (option::is_some(&maybe_idx)) {
  48. let idx = option::extract(&mut maybe_idx);
  49. // Remove by swapping with last (order not preserved), discard removed value
  50. let _ = vector::swap_remove(&mut s.trusted_signers, idx);
  51. } else {
  52. option::destroy_none(maybe_idx);
  53. abort ESignerNotFound
  54. };
  55. return
  56. };
  57. if (option::is_some(&maybe_idx)) {
  58. let idx = option::extract(&mut maybe_idx);
  59. let info_ref = vector::borrow_mut(&mut s.trusted_signers, idx);
  60. info_ref.expires_at = expires_at
  61. } else {
  62. option::destroy_none(maybe_idx);
  63. vector::push_back(
  64. &mut s.trusted_signers,
  65. TrustedSignerInfo { public_key: pubkey, expires_at },
  66. )
  67. }
  68. }
  69. public fun find_signer_index(
  70. signers: &vector<TrustedSignerInfo>,
  71. public_key: &vector<u8>,
  72. ): Option<u64> {
  73. let len = vector::length(signers);
  74. let mut i: u64 = 0;
  75. while (i < (len as u64)) {
  76. let info_ref = vector::borrow(signers, i);
  77. if (*public_key(info_ref) == *public_key) {
  78. return option::some(i)
  79. };
  80. i = i + 1
  81. };
  82. option::none()
  83. }
  84. #[test_only]
  85. public fun new_for_test(ctx: &mut TxContext): State {
  86. State {
  87. id: object::new(ctx),
  88. trusted_signers: vector::empty<TrustedSignerInfo>(),
  89. }
  90. }
  91. #[test_only]
  92. public fun destroy_for_test(s: State) {
  93. let State { id, trusted_signers } = s;
  94. let _ = trusted_signers;
  95. object::delete(id);
  96. }
  97. #[test]
  98. public fun test_add_new_signer() {
  99. let mut ctx = tx_context::dummy();
  100. let mut s = new_for_test(&mut ctx);
  101. let admin_cap = admin::mint_for_test(&mut ctx);
  102. let pk = x"030102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
  103. let expiry: u64 = 123;
  104. update_trusted_signer(&admin_cap, &mut s, pk, expiry);
  105. let signers_ref = get_trusted_signers(&s);
  106. assert!(vector::length(signers_ref) == 1, 100);
  107. let info = vector::borrow(signers_ref, 0);
  108. assert!(expires_at(info) == 123, 101);
  109. let got_pk = public_key(info);
  110. assert!(vector::length(got_pk) == (SECP256K1_COMPRESSED_PUBKEY_LEN as u64), 102);
  111. let State { id, trusted_signers } = s;
  112. let _ = trusted_signers;
  113. object::delete(id);
  114. admin::destroy_for_test(admin_cap);
  115. }
  116. #[test]
  117. public fun test_update_existing_signer_expiry() {
  118. let mut ctx = tx_context::dummy();
  119. let mut s = new_for_test(&mut ctx);
  120. let admin_cap = admin::mint_for_test(&mut ctx);
  121. update_trusted_signer(
  122. &admin_cap,
  123. &mut s,
  124. x"032a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a",
  125. 1000,
  126. );
  127. update_trusted_signer(
  128. &admin_cap,
  129. &mut s,
  130. x"032a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a",
  131. 2000,
  132. );
  133. let signers_ref = get_trusted_signers(&s);
  134. assert!(vector::length(signers_ref) == 1, 110);
  135. let info = vector::borrow(signers_ref, 0);
  136. assert!(expires_at(info) == 2000, 111);
  137. let State { id, trusted_signers } = s;
  138. let _ = trusted_signers;
  139. object::delete(id);
  140. admin::destroy_for_test(admin_cap);
  141. }
  142. #[test]
  143. public fun test_remove_signer_by_zero_expiry() {
  144. let mut ctx = tx_context::dummy();
  145. let mut s = new_for_test(&mut ctx);
  146. let admin_cap = admin::mint_for_test(&mut ctx);
  147. update_trusted_signer(
  148. &admin_cap,
  149. &mut s,
  150. x"030707070707070707070707070707070707070707070707070707070707070707",
  151. 999,
  152. );
  153. update_trusted_signer(
  154. &admin_cap,
  155. &mut s,
  156. x"030707070707070707070707070707070707070707070707070707070707070707",
  157. 0,
  158. );
  159. let signers_ref = get_trusted_signers(&s);
  160. assert!(vector::length(signers_ref) == 0, 120);
  161. let State { id, trusted_signers } = s;
  162. let _ = trusted_signers;
  163. object::delete(id);
  164. admin::destroy_for_test(admin_cap);
  165. }
  166. #[test, expected_failure(abort_code = EInvalidPubkeyLen)]
  167. public fun test_invalid_pubkey_length_rejected() {
  168. let mut ctx = tx_context::dummy();
  169. let mut s = new_for_test(&mut ctx);
  170. let admin_cap = admin::mint_for_test(&mut ctx);
  171. let short_pk = x"010203";
  172. update_trusted_signer(&admin_cap, &mut s, short_pk, 1);
  173. let State { id, trusted_signers } = s;
  174. let _ = trusted_signers;
  175. object::delete(id);
  176. admin::destroy_for_test(admin_cap);
  177. }
  178. #[test, expected_failure(abort_code = ESignerNotFound)]
  179. public fun test_remove_nonexistent_signer_fails() {
  180. let mut ctx = tx_context::dummy();
  181. let mut s = new_for_test(&mut ctx);
  182. let admin_cap = admin::mint_for_test(&mut ctx);
  183. // Try to remove a signer that doesn't exist by setting expires_at to 0
  184. update_trusted_signer(
  185. &admin_cap,
  186. &mut s,
  187. x"03aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  188. 0,
  189. );
  190. let State { id, trusted_signers } = s;
  191. let _ = trusted_signers;
  192. object::delete(id);
  193. admin::destroy_for_test(admin_cap);
  194. }