token_id.rs 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. use bigint::U256;
  2. use cosmwasm_std::{
  3. StdError,
  4. StdResult,
  5. Storage,
  6. };
  7. use sha3::{
  8. Digest,
  9. Keccak256,
  10. };
  11. use wormhole::byte_utils::ByteUtils;
  12. use crate::{
  13. state::{
  14. token_id_hashes,
  15. token_id_hashes_read,
  16. },
  17. CHAIN_ID,
  18. };
  19. // NOTE: [External and internal token id conversion]
  20. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  21. //
  22. // The CW721 NFT standard allows token ids to be arbitrarily long (utf8)
  23. // strings, while the token_ids in VAA payloads are always 32 bytes (and not
  24. // necessarily valid utf8).
  25. //
  26. // We call a token id that's in string format an "internal id", and a token id
  27. // that's in 32 byte format an "external id". Note that whether a token id is in
  28. // internal format or external format doesn't imply which chain the token id
  29. // originates from. We can have a terra (native) token id in both internal and
  30. // external formats, and likewise we can have an ethereum token in in both
  31. // internal and external formats.
  32. //
  33. // To support seamless transfers through the bridge, we need a way to have a
  34. // 1-to-1 mapping from internal ids to external ids.
  35. // When a foreign (such as ethereum or solana) token id first comes through, we
  36. // simply render it into a string by formatting it as a decimal number. Then,
  37. // when we want to transfer such a token back through the bridge, we simply
  38. // parse the string back into a u256 (32 byte) number.
  39. //
  40. // When a native token id first leaves through the bridge, we turn its id into a
  41. // 32 byte hash (keccak256). This hash is the external id. We store a mapping
  42. //
  43. // (chain_id, nft_address, keccak256(internal_id)) => internal_id
  44. //
  45. // so that we can turn it back into an internal id when it comes back through
  46. // the bridge. When the token is sent back, we could choose to delete the hash
  47. // from the store, but we do not. This way, external token verifiers will be
  48. // able to verify NFT origins even for NFTs that have been transferred back.
  49. //
  50. // If two token ids within the same contract have the same keccak256 hash, then
  51. // it's possible to lose tokens, but this is very unlikely.
  52. pub fn from_external_token_id(
  53. storage: &mut dyn Storage,
  54. nft_chain: u16,
  55. nft_address: &[u8; 32],
  56. token_id_external: &[u8; 32],
  57. ) -> StdResult<String> {
  58. if nft_chain == CHAIN_ID {
  59. token_id_hashes_read(storage, nft_chain, *nft_address).load(token_id_external)
  60. } else {
  61. Ok(format!("{}", U256::from_big_endian(token_id_external)))
  62. }
  63. }
  64. fn hash(token_id: &String) -> Vec<u8> {
  65. let mut hasher = Keccak256::new();
  66. hasher.update(token_id);
  67. hasher.finalize().to_vec()
  68. }
  69. pub fn to_external_token_id(
  70. storage: &mut dyn Storage,
  71. nft_chain: u16,
  72. nft_address: &[u8; 32],
  73. token_id_internal: String,
  74. ) -> StdResult<[u8; 32]> {
  75. if nft_chain == CHAIN_ID {
  76. let hash = hash(&token_id_internal);
  77. token_id_hashes(storage, nft_chain, *nft_address).save(&hash, &token_id_internal)?;
  78. Ok(hash.as_slice().get_const_bytes(0))
  79. } else {
  80. let mut bytes = [0; 32];
  81. U256::from_dec_str(&token_id_internal)
  82. .map_err(|_| {
  83. StdError::generic_err(format!(
  84. "{} could not be parsed as a decimal number",
  85. token_id_internal
  86. ))
  87. })?
  88. .to_big_endian(&mut bytes);
  89. Ok(bytes)
  90. }
  91. }