From df9d363bc99a25506f87c6fe250272eced1df6a8 Mon Sep 17 00:00:00 2001 From: Miles Zhang Date: Wed, 10 Jan 2024 13:27:05 +0800 Subject: [PATCH 1/3] chore: add xudt mainnet code hash configuration Signed-off-by: Miles Zhang --- config/settings.mainnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/settings.mainnet.yml b/config/settings.mainnet.yml index 7662437c7..e1cef37d6 100644 --- a/config/settings.mainnet.yml +++ b/config/settings.mainnet.yml @@ -29,7 +29,7 @@ omiga_inscription_info_code_hash: "" omiga_inscription_code_hash: "" # xudt -xudt_code_hash: "" +xudt_code_hash: "0x50bd8d6680b8b9cf98b73f3c08faf8b2a21914311954118ad6609be6e78a1b95" # hash length of an attribute(especially which comes from bytea column), # e.g. Block.uncle_block_hashes: "0x587f354162afd133b4a4f7a4b621d11e043c3c08b0af2801f1686b5403b14953", which has a length of 66 ( 2 + 64) From 9f4da26480ff486441aa311229c2a8b65abeed24 Mon Sep 17 00:00:00 2001 From: Miles Zhang Date: Wed, 10 Jan 2024 14:34:48 +0800 Subject: [PATCH 2/3] feat: return omiga inscription udt account for address api Signed-off-by: Miles Zhang --- app/serializers/address_serializer.rb | 42 +++++--- .../api/v1/addresses_controller_test.rb | 97 +++++++++++++------ test/factories/udt.rb | 3 - 3 files changed, 97 insertions(+), 45 deletions(-) diff --git a/app/serializers/address_serializer.rb b/app/serializers/address_serializer.rb index bd5155e03..f42bcec68 100644 --- a/app/serializers/address_serializer.rb +++ b/app/serializers/address_serializer.rb @@ -21,7 +21,9 @@ class AddressSerializer attribute :is_special do |object| object.special?.to_s end - attribute :special_address, if: Proc.new { |record| record.special? } do |object| + attribute :special_address, if: Proc.new { |record| + record.special? + } do |object| Settings.special_addresses[object.address_hash] end attribute :live_cells_count do |object| @@ -45,7 +47,19 @@ class AddressSerializer udt_icon_file: udt_account.udt_icon_file, udt_type: udt_account.udt_type, display_name: udt_account.display_name, - uan: udt_account.uan + uan: udt_account.uan, + } + elsif udt_account.udt_type == "omiga_inscription" + info = udt_account.udt.omiga_inscription_info + { + symbol: udt_account.symbol, + decimal: udt_account.decimal.to_s, + amount: udt_account.amount.to_s, + type_hash: udt_account.type_hash, + udt_type: udt_account.udt_type, + udt_amount: udt_account.udt.total_amount.to_s, + expected_supply: info.expected_supply.to_s, + mint_status: info.mint_status, } elsif udt_account.udt_type == "m_nft_token" ts = TypeScript.find_by script_hash: udt_account.type_hash @@ -59,20 +73,20 @@ class AddressSerializer amount: udt_account.amount.to_s, type_hash: udt_account.type_hash, collection: { - type_hash: coll&.type_script&.script_hash + type_hash: coll&.type_script&.script_hash, }, udt_icon_file: udt_account.udt_icon_file, - udt_type: udt_account.udt_type + udt_type: udt_account.udt_type, } elsif udt_account.udt_type == "nrc_721_token" udt = udt_account.udt Sentry.capture_message("Missing nrc_factory_cell", extra: { - address: object.address_hash, - udt: udt.symbol, - full_name: udt.full_name, - code_hash: udt.code_hash, - args: udt.args - }) + address: object.address_hash, + udt: udt.symbol, + full_name: udt.full_name, + code_hash: udt.code_hash, + args: udt.args, + }) factory_cell = udt_account.udt.nrc_factory_cell coll = factory_cell&.token_collection { @@ -80,10 +94,10 @@ class AddressSerializer amount: udt_account.nft_token_id.to_s, type_hash: udt_account.type_hash, collection: { - type_hash: coll&.type_script&.script_hash + type_hash: coll&.type_script&.script_hash, }, udt_icon_file: "#{udt_account.udt.nrc_factory_cell&.base_token_uri}/#{udt_account.nft_token_id}", - udt_type: udt_account.udt_type + udt_type: udt_account.udt_type, } elsif udt_account.udt_type == "spore_cell" ts = TypeScript.where(script_hash: udt_account.type_hash).first @@ -97,10 +111,10 @@ class AddressSerializer amount: udt_account.nft_token_id.to_s, type_hash: udt_account.type_hash, collection: { - type_hash: coll&.type_script&.script_hash + type_hash: coll&.type_script&.script_hash, }, udt_icon_file: data, - udt_type: udt_account.udt_type + udt_type: udt_account.udt_type, } end end diff --git a/test/controllers/api/v1/addresses_controller_test.rb b/test/controllers/api/v1/addresses_controller_test.rb index 41b1b321e..f8e2ff670 100644 --- a/test/controllers/api/v1/addresses_controller_test.rb +++ b/test/controllers/api/v1/addresses_controller_test.rb @@ -22,7 +22,8 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest test "should respond with 415 Unsupported Media Type when Content-Type is wrong" do address = create(:address, :with_lock_script) - get api_v1_address_url(address.address_hash), headers: { "Content-Type": "text/plain" } + get api_v1_address_url(address.address_hash), + headers: { "Content-Type": "text/plain" } assert_equal 415, response.status end @@ -30,9 +31,11 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest test "should respond with error object when Content-Type is wrong" do address = create(:address, :with_lock_script) error_object = Api::V1::Exceptions::InvalidContentTypeError.new - response_json = RequestErrorSerializer.new([error_object], message: error_object.title).serialized_json + response_json = RequestErrorSerializer.new([error_object], + message: error_object.title).serialized_json - get api_v1_address_url(address.address_hash), headers: { "Content-Type": "text/plain" } + get api_v1_address_url(address.address_hash), + headers: { "Content-Type": "text/plain" } assert_equal response_json, response.body end @@ -41,7 +44,8 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest address = create(:address, :with_lock_script) get api_v1_address_url(address.address_hash), - headers: { "Content-Type": "application/vnd.api+json", "Accept": "application/json" } + headers: { "Content-Type": "application/vnd.api+json", + "Accept": "application/json" } assert_equal 406, response.status end @@ -49,17 +53,20 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest test "should respond with error object when Accept is wrong" do address = create(:address, :with_lock_script) error_object = Api::V1::Exceptions::InvalidAcceptError.new - response_json = RequestErrorSerializer.new([error_object], message: error_object.title).serialized_json + response_json = RequestErrorSerializer.new([error_object], + message: error_object.title).serialized_json get api_v1_address_url(address.address_hash), - headers: { "Content-Type": "application/vnd.api+json", "Accept": "application/json" } + headers: { "Content-Type": "application/vnd.api+json", + "Accept": "application/json" } assert_equal response_json, response.body end test "should return error object when id is not a address hash" do error_object = Api::V1::Exceptions::AddressHashInvalidError.new - response_json = RequestErrorSerializer.new([error_object], message: error_object.title).serialized_json + response_json = RequestErrorSerializer.new([error_object], + message: error_object.title).serialized_json valid_get api_v1_address_url("9034fwefwef") @@ -72,7 +79,8 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest valid_get api_v1_address_url(address.address_hash) - assert_equal AddressSerializer.new(address).serialized_json, response.body + assert_equal AddressSerializer.new(address).serialized_json, + response.body end test "should return corresponding data with given lock hash" do @@ -80,7 +88,8 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest valid_get api_v1_address_url(address.lock_hash) - assert_equal LockHashSerializer.new(address).serialized_json, response.body + assert_equal LockHashSerializer.new(address).serialized_json, + response.body end test "should contain right keys in the serialized object when call show" do @@ -104,26 +113,31 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest end test "should return special address when query address is special" do - address = create(:address, :with_lock_script, address_hash: "ckb1qyq0hcfpff4h8w8zvy44uurvlgdrr09tefwqx266dl") + address = create(:address, :with_lock_script, + address_hash: "ckb1qyq0hcfpff4h8w8zvy44uurvlgdrr09tefwqx266dl") valid_get api_v1_address_url(address.address_hash) - assert_equal Settings.special_addresses[address.address_hash], json.dig("data", "attributes", "special_address") + assert_equal Settings.special_addresses[address.address_hash], + json.dig("data", "attributes", "special_address") end test "should not return special address when query address is not special" do - address = create(:address, :with_lock_script, address_hash: "ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq") + address = create(:address, :with_lock_script, + address_hash: "ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq") valid_get api_v1_address_url(address.address_hash) assert_nil json.dig("data", "attributes", "special_address") end test "should support full address query when short address's lock script exists" do - address = create(:address, :with_lock_script, address_hash: "ckb1qyqt8xaupvm8837nv3gtc9x0ekkj64vud3jqfwyw5v") + address = create(:address, :with_lock_script, + address_hash: "ckb1qyqt8xaupvm8837nv3gtc9x0ekkj64vud3jqfwyw5v") query_key = "ckb1qjda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3vumhs9nvu786dj9p0q5elx66t24n3kxgj53qks" address.query_address = query_key valid_get api_v1_address_url(query_key) - assert_equal AddressSerializer.new(address).serialized_json, response.body + assert_equal AddressSerializer.new(address).serialized_json, + response.body end test "should support short address query when full address's lock script exists" do @@ -133,12 +147,13 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest address.query_address = query_key valid_get api_v1_address_url(query_key) - assert_equal AddressSerializer.new(address).serialized_json, response.body + assert_equal AddressSerializer.new(address).serialized_json, + response.body end test "should return published udt accounts with given address hash" do address = create(:address, :with_lock_script) - udt_account = create(:udt_account, published: true, address: address) + udt_account = create(:udt_account, published: true, address:) address.query_address = address.address_hash valid_get api_v1_address_url(address.address_hash) @@ -151,14 +166,14 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest "udt_icon_file" => udt_account.udt_icon_file, "udt_type" => udt_account.udt_type, "display_name" => nil, - "uan" => nil - } + "uan" => nil, + }, ], json.dig("data", "attributes", "udt_accounts") end test "should not return unpublished udt accounts with given address hash" do address = create(:address, :with_lock_script) - create(:udt_account, address: address) + create(:udt_account, address:) address.query_address = address.address_hash valid_get api_v1_address_url(address.address_hash) @@ -167,7 +182,8 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest end test "should return balance occupied" do - address = create(:address, :with_lock_script, address_hash: "ckb1qyq0hcfpff4h8w8zvy44uurvlgdrr09tefwqx266dl") + address = create(:address, :with_lock_script, + address_hash: "ckb1qyq0hcfpff4h8w8zvy44uurvlgdrr09tefwqx266dl") valid_get api_v1_address_url(address.address_hash) assert_equal "0", json.dig("data", "attributes", "balance_occupied") @@ -186,7 +202,7 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest args: type_script.args) udt = create(:udt, udt_type: "nrc_721_token", nrc_factory_cell_id: factory_cell.id, full_name: "OldName", symbol: "ON") - udt_account = create(:udt_account, published: true, address: address, udt_id: udt.id, nft_token_id: "1a2b3c", + udt_account = create(:udt_account, published: true, address:, udt_id: udt.id, nft_token_id: "1a2b3c", udt_type: "nrc_721_token") address.query_address = address.address_hash valid_get api_v1_address_url(address.address_hash) @@ -198,20 +214,22 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest "type_hash" => nil, "udt_icon_file" => "https://dev.nrc.com/1a2b3c", "udt_type" => udt_account.udt_type, - "collection" => { "type_hash" => type_script.script_hash } - } + "collection" => { "type_hash" => type_script.script_hash }, + }, ], json.dig("data", "attributes", "udt_accounts") end test "should return spore cell udt accounts with given address hash" do - output = create :cell_output, :with_full_transaction, cell_type: "spore_cell" + output = create :cell_output, :with_full_transaction, + cell_type: "spore_cell" cell_data = create :cell_datum, cell_output: output cluster_type = create :type_script - tc = create :token_collection, type_script: cluster_type, standard: "spore" + tc = create :token_collection, type_script: cluster_type, + standard: "spore" create :token_item, collection_id: tc.id, cell_id: output.id address = create(:address, :with_lock_script) udt = create(:udt, udt_type: "spore_cell", full_name: "SporeTest") - udt_account = create(:udt_account, full_name: udt.full_name, published: true, address: address, udt_id: udt.id, nft_token_id: "123456", + udt_account = create(:udt_account, full_name: udt.full_name, published: true, address:, udt_id: udt.id, nft_token_id: "123456", udt_type: "spore_cell", type_hash: output.type_script.script_hash) address.query_address = address.address_hash valid_get api_v1_address_url(address.address_hash) @@ -222,8 +240,31 @@ class AddressesControllerTest < ActionDispatch::IntegrationTest "type_hash" => output.type_script.script_hash, "udt_icon_file" => cell_data.hex_data, "udt_type" => udt_account.udt_type, - "collection" => { "type_hash" => cluster_type.script_hash } - } + "collection" => { "type_hash" => cluster_type.script_hash }, + }, + ], json.dig("data", "attributes", "udt_accounts") + end + + test "should return omiga inscription udt accounts with given address hash" do + udt = create(:udt, :omiga_inscription, full_name: "CKB Fist Inscription", + symbol: "CKBI", decimal: 8) + info = udt.omiga_inscription_info + address = create(:address, :with_lock_script) + udt_account = create(:udt_account, symbol: udt.symbol, full_name: udt.full_name, decimal: udt.decimal, published: true, address:, udt_id: udt.id, + udt_type: "omiga_inscription", type_hash: udt.type_hash, amount: info.mint_limit) + address.query_address = address.address_hash + valid_get api_v1_address_url(address.address_hash) + assert_equal [ + { + "symbol" => udt.symbol, + "decimal" => udt.decimal.to_s, + "amount" => udt_account.amount.to_s, + "type_hash" => udt.type_hash, + "udt_type" => udt_account.udt_type, + "udt_amount" => udt_account.udt.total_amount.to_s, + "expected_supply" => info.expected_supply.to_s, + "mint_status" => info.mint_status, + }, ], json.dig("data", "attributes", "udt_accounts") end end diff --git a/test/factories/udt.rb b/test/factories/udt.rb index 8b9fd26da..bd8eb033c 100644 --- a/test/factories/udt.rb +++ b/test/factories/udt.rb @@ -38,9 +38,6 @@ trait :omiga_inscription do udt_type { "omiga_inscription" } - decimal { 0.8e1 } - full_name { "CKB Fist Inscription" } - symbol { "CKBI" } published { true } after(:create) do |udt, _evaluator| create(:omiga_inscription_info, From f284af13ee790ef2775c93ffad316543bcbc8d27 Mon Sep 17 00:00:00 2001 From: Miles Zhang Date: Thu, 11 Jan 2024 14:15:11 +0800 Subject: [PATCH 3/3] feat: update omiga inscription task Signed-off-by: Miles Zhang --- .../update_omiga_inscription_udt.rake | 86 +++++++++++++++++++ .../update_omiga_inscription_udt_test.rb | 76 ++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 lib/tasks/migration/update_omiga_inscription_udt.rake create mode 100644 test/tasks/migrations/update_omiga_inscription_udt_test.rb diff --git a/lib/tasks/migration/update_omiga_inscription_udt.rake b/lib/tasks/migration/update_omiga_inscription_udt.rake new file mode 100644 index 000000000..2aa452a0f --- /dev/null +++ b/lib/tasks/migration/update_omiga_inscription_udt.rake @@ -0,0 +1,86 @@ +namespace :migration do + desc "Usage: RAILS_ENV=production bundle exec rake migration:update_omiga_inscription_udt" + task update_omiga_inscription_udt: :environment do + info_ts_ids = TypeScript.where(code_hash: CkbSync::Api.instance.omiga_inscription_info_code_hash).pluck(:id) + info_outputs = CellOutput.where(type_script_id: info_ts_ids) + info_outputs.update_all(cell_type: "omiga_inscription_info") + + info_outputs.each do |output| + info = CkbUtils.parse_omiga_inscription_info(output.data) + # ignore old version data + if output.data.slice(-2..-1).in?(["00", "01", "02"]) + OmigaInscriptionInfo.upsert(info.merge(output.type_script.to_node), + unique_by: :udt_hash) + end + end + + xudt_ts_ids = TypeScript.where(code_hash: CkbSync::Api.instance.xudt_code_hash).pluck(:id) + xudt_ts_ids.each do |tid| + xudt_outputs = CellOutput.where(type_script_id: tid) + xudt_outputs.each do |output| + if OmigaInscriptionInfo.where(udt_hash: output.type_hash).exists? + info = OmigaInscriptionInfo.find_by!(udt_hash: output.type_hash) + output.update(cell_type: "omiga_inscription", + udt_amount: info.mint_limit) + if info.udt_id.nil? + nft_token_attr = {} + nft_token_attr[:full_name] = info.name.presence + nft_token_attr[:symbol] = info.symbol.presence + nft_token_attr[:decimal] = info.decimal + nft_token_attr[:published] = true + udt = Udt.create_or_find_by!({ + type_hash: output.type_hash, + udt_type: "omiga_inscription", + block_timestamp: output.block.timestamp, + args: output.type_script.args, + code_hash: output.type_script.code_hash, + hash_type: output.type_script.hash_type, + }.merge(nft_token_attr)) + info.update!(udt_id: udt.id) + end + else + output.update(cell_type: "xudt") + end + end + end + + # udt_transaction + Udt.where(udt_type: "omiga_inscription").each do |udt| + outputs = CellOutput.omiga_inscription.where(type_hash: udt.type_hash).select( + :address_id, :ckb_transaction_id + ).distinct + udt_transaction_attrs = + outputs.map do |output| + { udt_id: udt.id, ckb_transaction_id: output.ckb_transaction_id } + end + UdtTransaction.insert_all(udt_transaction_attrs) + + address_udt_transaction_attrs = + outputs.map do |output| + { address_id: output.address_id, + ckb_transaction_id: output.ckb_transaction_id } + end + AddressUdtTransaction.insert_all(address_udt_transaction_attrs) + end + + # udt_account + Udt.where(udt_type: "omiga_inscription").each do |udt| + # {address_id => udt_amount} + results = CellOutput.live.omiga_inscription.where(type_hash: udt.type_hash).select(:address_id).group(:address_id).sum(:udt_amount) + attrs = + results.map do |address_id, udt_amount| + { + address_id:, udt_type: udt.udt_type, full_name: udt.full_name, symbol: udt.symbol, decimal: udt.decimal, + published: udt.published, code_hash: udt.code_hash, type_hash: udt.type_hash, amount: udt_amount, udt_id: udt.id + } + end + + unless attrs.empty? + UdtAccount.insert_all(attrs) + udt.update(total_amount: results.sum { |_k, v| v }) + end + end + + puts "done" + end +end diff --git a/test/tasks/migrations/update_omiga_inscription_udt_test.rb b/test/tasks/migrations/update_omiga_inscription_udt_test.rb new file mode 100644 index 000000000..0e2547f94 --- /dev/null +++ b/test/tasks/migrations/update_omiga_inscription_udt_test.rb @@ -0,0 +1,76 @@ +require "test_helper" +require "rake" + +class UpdateOmigaInscriptionUdtTest < ActiveSupport::TestCase + setup do + Server::Application.load_tasks if Rake::Task.tasks.empty? + end + + test "update omiga inscription" do + CkbSync::Api.any_instance.stubs(:xudt_code_hash).returns("0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb") + CkbSync::Api.any_instance.stubs(:omiga_inscription_info_code_hash).returns("0x50fdea2d0030a8d0b3d69f883b471cab2a29cae6f01923f19cecac0f27fdaaa6") + + block1 = create(:block, :with_block_hash, number: 0) + tx1 = create(:ckb_transaction, block: block1, + tx_hash: "0x3e89753ebca825e1504498eb18b56576d5b7eff59fe033346a10ab9e8ca359a4") + input_address1 = create(:address) + address1_lock = create(:lock_script, address_id: input_address1.id) + info_ts = create(:type_script, + args: "0xcd89d8f36593a9a82501c024c5cdc4877ca11c5b3d5831b3e78334aecb978f0d", + code_hash: "0x50fdea2d0030a8d0b3d69f883b471cab2a29cae6f01923f19cecac0f27fdaaa6", + hash_type: "type") + info_output = create(:cell_output, ckb_transaction: tx1, + block: block1, capacity: 50000000 * 10**8, + tx_hash: tx1.tx_hash, + cell_index: 1, + address: input_address1, + cell_type: "normal", + lock_script_id: address1_lock.id, + type_script_id: info_ts.id) + info_output.data = "0x0814434b42204669737420496e736372697074696f6e04434b42495fa66c8d5f43914f85d3083e0529931883a5b0a14282f891201069f1b50679080040075af0750700000000000000000000e8764817000000000000000000000000" + + input_address2 = create(:address) + address2_lock = create(:lock_script, address_id: input_address2.id) + + xudt_ts = create(:type_script, + args: "0x9709d30fc21348ae1d28a197310a80aec3b8cdb5c93814d5e240f9fba85b76af", + code_hash: "0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb", + hash_type: "type", + script_hash: "0x5fa66c8d5f43914f85d3083e0529931883a5b0a14282f891201069f1b5067908") + block2 = create(:block, :with_block_hash, number: 1) + tx2 = create(:ckb_transaction, block: block2, + tx_hash: "0xd5d38a2096c10e5d0d55def7f2b3fe58779aad831fbc9dcd594446b1f0837430") + xudt_output = create(:cell_output, ckb_transaction: tx2, + block: block2, capacity: 50000000 * 10**8, + tx_hash: tx2.tx_hash, + type_hash: xudt_ts.script_hash, + cell_index: 1, + address: input_address2, + cell_type: "normal", + lock_script_id: address2_lock.id, + type_script_id: xudt_ts.id) + input_address3 = create(:address) + address3_lock = create(:lock_script, address_id: input_address3.id) + tx3 = create(:ckb_transaction, block: block2, + tx_hash: "0xd5d38a2096c10e5d0d55def7f2b3fe58779aad831fbc9dcd594446b1f0837431") + xudt3_output = create(:cell_output, ckb_transaction: tx3, + block: block2, capacity: 50000000 * 10**8, + tx_hash: tx3.tx_hash, + type_hash: xudt_ts.script_hash, + cell_index: 1, + address: input_address3, + cell_type: "normal", + lock_script_id: address3_lock.id, + type_script_id: xudt_ts.id) + + xudt3_output.data = "0x00e87648170000000000000000000000" + Rake::Task["migration:update_omiga_inscription_udt"].execute + assert_equal 1, OmigaInscriptionInfo.count + assert_equal 1, Udt.count + assert_equal 2, UdtTransaction.count + assert_equal 2, AddressUdtTransaction.count + assert_equal 2, UdtAccount.count + assert_equal 100000000000, UdtAccount.first.amount + assert_equal 200000000000, Udt.first.total_amount + end +end