diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fac8e31a..ed096f644 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,21 +22,25 @@ jobs: image-name: '${{ steps.docker_build.outputs.imageFullName }}' image-tag: '${{ steps.docker_build.outputs.tags }}' steps: -# - name: Free Disk Space (Ubuntu) -# uses: jlumbroso/free-disk-space@main -# with: -# # this might remove tools that are actually needed, -# # if set to "true" but frees about 6 GB -# tool-cache: false -# -# # all of these default to true, but feel free to set to -# # "false" if necessary for your workflow -# android: true -# dotnet: true -# haskell: true -# large-packages: true -# docker-images: true -# swap-storage: true + - name: Update Package List and Remove Dotnet + run: | + sudo apt-get update + sudo apt-get remove -y '^dotnet-.*' + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + # this might remove tools that are actually needed, + # if set to "true" but frees about 6 GB + tool-cache: false + + # all of these default to true, but feel free to set to + # "false" if necessary for your workflow + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true - uses: actions/checkout@v3 with: fetch-depth: 0 diff --git a/app/controllers/api/v1/udt_verifications_controller.rb b/app/controllers/api/v1/udt_verifications_controller.rb new file mode 100644 index 000000000..2ee731e8e --- /dev/null +++ b/app/controllers/api/v1/udt_verifications_controller.rb @@ -0,0 +1,31 @@ +module Api + module V1 + class UdtVerificationsController < ApplicationController + before_action :check_udt_info, only: :update + before_action :set_locale, only: :update + + def update + udt_verification = UdtVerification.find_or_create_by(udt_id: @udt.id) + + udt_verification.refresh_token!(request.remote_ip) + UdtVerificationMailer.with(email: @udt.email, token: udt_verification.token, + locale: @locale).send_token.deliver_later + render json: :ok + rescue UdtVerification::TokenSentTooFrequentlyError + raise Api::V1::Exceptions::TokenSentTooFrequentlyError + end + + private + + def check_udt_info + @udt = Udt.find_by(type_hash: params[:id]) + raise Api::V1::Exceptions::UdtNotFoundError if @udt.nil? + raise Api::V1::Exceptions::UdtNoContactEmailError if @udt.email.blank? + end + + def set_locale + @locale = params[:locale] == "zh_CN" ? "zh_CN" : "en" + end + end + end +end diff --git a/app/controllers/api/v1/udts_controller.rb b/app/controllers/api/v1/udts_controller.rb index e9f598c90..3ad82eae0 100644 --- a/app/controllers/api/v1/udts_controller.rb +++ b/app/controllers/api/v1/udts_controller.rb @@ -34,14 +34,27 @@ def update icon_file: params[:icon_file], uan: params[:uan], display_name: params[:display_name], - contact_info: params[:contact_info] + email: params[:email] } - udt.update!(attrs) + if udt.email.blank? + raise Api::V1::Exceptions::UdtInfoInvalidError.new("Email can't be blank") if params[:email].blank? + + udt.update!(attrs) + else + raise Api::V1::Exceptions::UdtVerificationNotFoundError if udt.udt_verification.nil? + + udt.udt_verification.validate_token!(params[:token]) + udt.update!(attrs) + end render json: :ok rescue ActiveRecord::RecordNotFound raise Api::V1::Exceptions::UdtNotFoundError rescue ActiveRecord::RecordInvalid => e raise Api::V1::Exceptions::UdtInfoInvalidError.new(e) + rescue UdtVerification::TokenExpiredError + raise Api::V1::Exceptions::TokenExpiredError + rescue UdtVerification::TokenNotMatchError + raise Api::V1::Exceptions::TokenNotMatchError end def show diff --git a/app/controllers/api/v2/scripts_controller.rb b/app/controllers/api/v2/scripts_controller.rb index 0378d3872..63c80ce39 100644 --- a/app/controllers/api/v2/scripts_controller.rb +++ b/app/controllers/api/v2/scripts_controller.rb @@ -9,12 +9,8 @@ class ScriptsController < BaseController def general_info head :not_found and return if @script.blank? || @contract.blank? - key = ["contract_info", @contract.code_hash, @contract.hash_type] - result = - Rails.cache.fetch(key, expires_in: 10.minutes) do - get_script_content - end - render json: { data: result } + expires_in 15.seconds, public: true, must_revalidate: true, stale_while_revalidate: 5.seconds + render json: { data: get_script_content } end def ckb_transactions @@ -31,23 +27,20 @@ def deployed_cells head :not_found and return if @contract.blank? expires_in 15.seconds, public: true, must_revalidate: true, stale_while_revalidate: 5.seconds - @deployed_cells = @contract.deployed_cells.page(@page).per(@page_size).fast_page + @deployed_cells = @contract.deployed_cell_outputs.live.page(@page).per(@page_size).fast_page end def referring_cells head :not_found and return if @contract.blank? expires_in 15.seconds, public: true, must_revalidate: true, stale_while_revalidate: 5.seconds - @referring_cells = @contract.referring_cells.page(@page).per(@page_size).fast_page + @referring_cells = @contract.referring_cell_outputs.live.page(@page).per(@page_size).fast_page end private def get_script_content - referring_cells = @contract&.referring_cell_outputs - deployed_cells = @contract&.deployed_cell_outputs&.live - transactions = @contract&.cell_dependencies - + deployed_cells = @contract.deployed_cell_outputs if deployed_cells.present? deployed_type_script = deployed_cells[0].type_script if deployed_type_script.code_hash == Settings.type_id_code_hash @@ -60,11 +53,11 @@ def get_script_content code_hash: @script.code_hash, hash_type: @script.hash_type, script_type: @script.class.to_s, - capacity_of_deployed_cells: deployed_cells&.sum(:capacity), - capacity_of_referring_cells: referring_cells&.sum(:capacity), - count_of_transactions: transactions&.count.to_i, - count_of_deployed_cells: deployed_cells&.count.to_i, - count_of_referring_cells: referring_cells&.count.to_i + capacity_of_deployed_cells: @contract.total_deployed_cells_capacity, + capacity_of_referring_cells: @contract.total_referring_cells_capacity, + count_of_transactions: @contract.ckb_transactions_count, + count_of_deployed_cells: @contract.deployed_cells_count, + count_of_referring_cells: @contract.referring_cells_count } end diff --git a/app/lib/api/v1/exceptions.rb b/app/lib/api/v1/exceptions.rb index 1b9dce5f8..c011015fb 100644 --- a/app/lib/api/v1/exceptions.rb +++ b/app/lib/api/v1/exceptions.rb @@ -174,11 +174,13 @@ def initialize super code: 1027, status: 404, title: "URI parameters invalid", detail: "code hash should be start with 0x", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" end end + class ScriptHashTypeParamsInvalidError < Error def initialize super code: 1028, status: 404, title: "URI parameters invalid", detail: "hash type should be 'type'", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" end end + class ScriptNotFoundError < Error def initialize super code: 1029, status: 404, title: "Script not found", detail: "Script not found", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" @@ -191,6 +193,41 @@ def initialize(detail) end end + class UdtVerificationInvalidError < Error + def initialize(detail) + super code: 1031, status: 400, title: "UDT verification invalid", detail: detail, href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" + end + end + + class UdtVerificationNotFoundError < Error + def initialize + super code: 1032, status: 404, title: "UDT Verification Not Found", detail: "No UDT verification records found by given type hash", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" + end + end + + class UdtNoContactEmailError < Error + def initialize + super code: 1033, status: 400, title: "UDT has no contact email", detail: "", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" + end + end + + class TokenExpiredError < Error + def initialize + super code: 1034, status: 400, title: "Token has expired", detail: "", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" + end + end + + class TokenNotMatchError < Error + def initialize + super code: 1035, status: 400, title: "Token is not matched", detail: "", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" + end + end + + class TokenSentTooFrequentlyError < Error + def initialize + super code: 1036, status: 400, title: "Token sent too frequently", detail: "", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" + end + end end end end diff --git a/app/mailers/udt_verification_mailer.rb b/app/mailers/udt_verification_mailer.rb new file mode 100644 index 000000000..886dc9419 --- /dev/null +++ b/app/mailers/udt_verification_mailer.rb @@ -0,0 +1,12 @@ +class UdtVerificationMailer < ApplicationMailer + default from: "noreply@magickbase.com" + + def send_token + email = params[:email] + @token = params[:token] + locale = params[:locale] || "en" + I18n.with_locale(locale) do + mail(to: email, subject: "Token Info Verification") + end + end +end diff --git a/app/models/contract.rb b/app/models/contract.rb index 115e6e5d5..f6aeee812 100644 --- a/app/models/contract.rb +++ b/app/models/contract.rb @@ -25,18 +25,23 @@ def self.create_initial_data # # Table name: contracts # -# id :bigint not null, primary key -# code_hash :binary -# hash_type :string -# deployed_args :string -# role :string default("type_script") -# name :string -# symbol :string -# description :string -# verified :boolean default(FALSE) -# created_at :datetime not null -# updated_at :datetime not null -# deprecated :boolean +# id :bigint not null, primary key +# code_hash :binary +# hash_type :string +# deployed_args :string +# role :string default("type_script") +# name :string +# symbol :string +# description :string +# verified :boolean default(FALSE) +# created_at :datetime not null +# updated_at :datetime not null +# deprecated :boolean +# ckb_transactions_count :decimal(30, ) default(0) +# deployed_cells_count :decimal(30, ) default(0) +# referring_cells_count :decimal(30, ) default(0) +# total_deployed_cells_capacity :decimal(30, ) default(0) +# total_referring_cells_capacity :decimal(30, ) default(0) # # Indexes # diff --git a/app/models/udt.rb b/app/models/udt.rb index 78aea622d..0b73b73b5 100644 --- a/app/models/udt.rb +++ b/app/models/udt.rb @@ -1,7 +1,10 @@ class Udt < ApplicationRecord + MAX_PAGINATES_PER = 100 + belongs_to :nrc_factory_cell, optional: true - MAX_PAGINATES_PER = 100 + has_one :udt_verification + enum udt_type: { sudt: 0, m_nft_token: 1, nrc_721_token: 2, spore_cell: 3 } validates_presence_of :total_amount @@ -9,6 +12,7 @@ class Udt < ApplicationRecord validates_length_of :full_name, minimum: 1, maximum: 100, allow_nil: true validates :decimal, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 39 }, allow_nil: true validates :total_amount, numericality: { greater_than_or_equal_to: 0 } + validates :email, format: { with: /\A(.+)@(.+)\z/, message: "Not a valid email" }, allow_nil: true attribute :code_hash, :ckb_hash @@ -60,7 +64,7 @@ def type_script # display_name :string # uan :string # h24_ckb_transactions_count :bigint default(0) -# contact_info :string +# email :string # # Indexes # diff --git a/app/models/udt_verification.rb b/app/models/udt_verification.rb new file mode 100644 index 000000000..f92c4276b --- /dev/null +++ b/app/models/udt_verification.rb @@ -0,0 +1,43 @@ +class UdtVerification < ApplicationRecord + SENT_FREQUENCY_MINUTES = 1 + KEEP_ALIVE_MINUTES = 10 + + class TokenExpiredError < StandardError; end + class TokenNotMatchError < StandardError; end + class TokenSentTooFrequentlyError < StandardError; end + + belongs_to :udt + + def refresh_token!(ip) + raise TokenSentTooFrequentlyError if sent_at.present? && self.sent_at + SENT_FREQUENCY_MINUTES.minutes > Time.now + + self.token = rand(999999).to_s.rjust(6, "0") + self.sent_at = Time.now + self.last_ip = ip + self.save! + end + + def validate_token!(token_params) + raise TokenExpiredError if self.sent_at + KEEP_ALIVE_MINUTES.minutes < Time.now + raise TokenNotMatchError if token != token_params.to_i + end +end + +# == Schema Information +# +# Table name: udt_verifications +# +# id :bigint not null, primary key +# token :integer +# sent_at :datetime +# last_ip :inet +# udt_id :bigint +# udt_type_hash :integer +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_udt_verifications_on_udt_id (udt_id) +# index_udt_verifications_on_udt_type_hash (udt_type_hash) UNIQUE +# diff --git a/app/utils/ckb_utils.rb b/app/utils/ckb_utils.rb index 2423c75e5..f0d63ad4d 100644 --- a/app/utils/ckb_utils.rb +++ b/app/utils/ckb_utils.rb @@ -560,6 +560,7 @@ def self.parse_spore_cluster_data(hex_data) description_offset = [data.slice(16, 8)].pack("H*").unpack1("l") * 2 name = [data.slice(name_offset + 8..description_offset - 1)].pack("H*") description = [data.slice(description_offset + 8..-1)].pack("H*") + name = "#{name[0,97]}..." if name.length > 100 { name: name, description: description } rescue => _e { name: nil, description: nil } diff --git a/app/views/api/v2/scripts/deployed_cells.json.jbuilder b/app/views/api/v2/scripts/deployed_cells.json.jbuilder index 7f84e408f..ddf21033d 100644 --- a/app/views/api/v2/scripts/deployed_cells.json.jbuilder +++ b/app/views/api/v2/scripts/deployed_cells.json.jbuilder @@ -1,27 +1,26 @@ json.data do json.deployed_cells @deployed_cells do |deployed_cell| - cell_output = deployed_cell.cell_output - json.id deployed_cell.cell_output.id - json.capacity deployed_cell.cell_output.capacity - json.ckb_transaction_id deployed_cell.cell_output.ckb_transaction_id - json.created_at deployed_cell.cell_output.created_at - json.updated_at deployed_cell.cell_output.updated_at - json.status cell_output.status - json.address_id cell_output.address_id - json.block_id cell_output.block_id - json.tx_hash cell_output.tx_hash - json.cell_index cell_output.cell_index - json.consumed_by_id cell_output.consumed_by_id - json.cell_type cell_output.cell_type - json.data_size cell_output.data_size - json.occupied_capacity cell_output.occupied_capacity - json.block_timestamp cell_output.block_timestamp - json.consumed_block_timestamp cell_output.consumed_block_timestamp - json.type_hash cell_output.type_hash - json.udt_amount cell_output.udt_amount - json.dao cell_output.dao - json.lock_script_id cell_output.lock_script_id - json.type_script_id cell_output.type_script_id + json.id deployed_cell.id + json.capacity deployed_cell.capacity + json.ckb_transaction_id deployed_cell.ckb_transaction_id + json.created_at deployed_cell.created_at + json.updated_at deployed_cell.updated_at + json.status deployed_cell.status + json.address_id deployed_cell.address_id + json.block_id deployed_cell.block_id + json.tx_hash deployed_cell.tx_hash + json.cell_index deployed_cell.cell_index + json.consumed_by_id deployed_cell.consumed_by_id + json.cell_type deployed_cell.cell_type + json.data_size deployed_cell.data_size + json.occupied_capacity deployed_cell.occupied_capacity + json.block_timestamp deployed_cell.block_timestamp + json.consumed_block_timestamp deployed_cell.consumed_block_timestamp + json.type_hash deployed_cell.type_hash + json.udt_amount deployed_cell.udt_amount + json.dao deployed_cell.dao + json.lock_script_id deployed_cell.lock_script_id + json.type_script_id deployed_cell.type_script_id end json.meta do json.total @deployed_cells.total_count diff --git a/app/views/api/v2/scripts/referring_cells.json.jbuilder b/app/views/api/v2/scripts/referring_cells.json.jbuilder index a4cc5f13f..c496c2e9e 100644 --- a/app/views/api/v2/scripts/referring_cells.json.jbuilder +++ b/app/views/api/v2/scripts/referring_cells.json.jbuilder @@ -1,27 +1,26 @@ json.data do json.referring_cells @referring_cells do |referring_cell| - cell_output = referring_cell.cell_output - json.id cell_output.id - json.capacity cell_output.capacity - json.ckb_transaction_id cell_output.ckb_transaction_id - json.created_at cell_output.created_at - json.updated_at cell_output.updated_at - json.status cell_output.status - json.address_id cell_output.address_id - json.block_id cell_output.block_id - json.tx_hash cell_output.tx_hash - json.cell_index cell_output.cell_index - json.consumed_by_id cell_output.consumed_by_id - json.cell_type cell_output.cell_type - json.data_size cell_output.data_size - json.occupied_capacity cell_output.occupied_capacity - json.block_timestamp cell_output.block_timestamp - json.consumed_block_timestamp cell_output.consumed_block_timestamp - json.type_hash cell_output.type_hash - json.udt_amount cell_output.udt_amount - json.dao cell_output.dao - json.lock_script_id cell_output.lock_script_id - json.type_script_id cell_output.type_script_id + json.id referring_cell.id + json.capacity referring_cell.capacity + json.ckb_transaction_id referring_cell.ckb_transaction_id + json.created_at referring_cell.created_at + json.updated_at referring_cell.updated_at + json.status referring_cell.status + json.address_id referring_cell.address_id + json.block_id referring_cell.block_id + json.tx_hash referring_cell.tx_hash + json.cell_index referring_cell.cell_index + json.consumed_by_id referring_cell.consumed_by_id + json.cell_type referring_cell.cell_type + json.data_size referring_cell.data_size + json.occupied_capacity referring_cell.occupied_capacity + json.block_timestamp referring_cell.block_timestamp + json.consumed_block_timestamp referring_cell.consumed_block_timestamp + json.type_hash referring_cell.type_hash + json.udt_amount referring_cell.udt_amount + json.dao referring_cell.dao + json.lock_script_id referring_cell.lock_script_id + json.type_script_id referring_cell.type_script_id end json.meta do json.total @referring_cells.total_count diff --git a/app/views/udt_verification_mailer/send_token.en.text.erb b/app/views/udt_verification_mailer/send_token.en.text.erb new file mode 100644 index 000000000..f167d24fb --- /dev/null +++ b/app/views/udt_verification_mailer/send_token.en.text.erb @@ -0,0 +1,12 @@ +Dear Token Info Author, + +To ensure the effectiveness of your Token Info modification operation, we have generated a verification code for you to complete the Token Info update. + +Your verification code is: <%= @token %> + +Please note that this verification code is only valid for the next 10 minutes. Please use it to complete your Token Info modification within this time frame and refrain from sharing your code with others. If you have not initiated any action or if this operation is not associated with you, please disregard this email. + +If you have any questions or require assistance, please feel free to contact our customer support team.Thank you for your trust and support! + +Best regards, +The MagicKBase Team diff --git a/app/views/udt_verification_mailer/send_token.zh_CN.text.erb b/app/views/udt_verification_mailer/send_token.zh_CN.text.erb new file mode 100644 index 000000000..c307071f8 --- /dev/null +++ b/app/views/udt_verification_mailer/send_token.zh_CN.text.erb @@ -0,0 +1,10 @@ +尊敬的 Token Info 作者, + +为了确保本次 Token Info 修改操作的有效性,我们为您生成了一个验证码,用于完成 Token Info 的修改操作。 + +您的验证码是:<%= @token %> + +请注意,该验证码仅在接下来的10分钟内有效。请在此时间内完成 Token Info 的修改操作,并且请不要分享您的验证码给他人。如果您没有进行任何操作或者不是您的操作,请忽略此邮件。 + +如果您有任何疑问或需要帮助,请随时联系我们的客户支持团队。谢谢您的信任和支持! +MagicKBase 团队 diff --git a/app/workers/contract_statistic_worker.rb b/app/workers/contract_statistic_worker.rb new file mode 100644 index 000000000..558132c41 --- /dev/null +++ b/app/workers/contract_statistic_worker.rb @@ -0,0 +1,20 @@ +class ContractStatisticWorker + include Sidekiq::Worker + sidekiq_options queue: "critical" + + def perform + Contract.find_each do |contract| + referring_cells = contract.referring_cell_outputs&.live + deployed_cells = contract.deployed_cell_outputs&.live + transactions = contract.cell_dependencies + + contract.update( + ckb_transactions_count: transactions.count, + deployed_cells_count: deployed_cells&.count.to_i, + referring_cells_count: referring_cells&.count.to_i, + total_deployed_cells_capacity: deployed_cells&.sum(:capacity), + total_referring_cells_capacity: referring_cells&.sum(:capacity) + ) + end + end +end diff --git a/config/environments/production.rb b/config/environments/production.rb index 4d2453274..d7172ea41 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -66,7 +66,19 @@ # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false + config.action_mailer.raise_delivery_errors = true + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { + address: ENV["SMTP_ADDRESS"], + port: ENV["SMTP_PORT"], + domain: "noreply@magickbase.com", + user_name: ENV["SMTP_USER"], + password: ENV["SMTP_PASSWORD"], + authentication: "plain", + enable_starttls_auto: true, + open_timeout: 5, + read_timeout: 5 + } # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). diff --git a/config/initializers/locales.rb b/config/initializers/locales.rb new file mode 100644 index 000000000..c7d592062 --- /dev/null +++ b/config/initializers/locales.rb @@ -0,0 +1,2 @@ +I18n.available_locales = [:en, :zh_CN] +I18n.default_locale = :en diff --git a/config/routes.rb b/config/routes.rb index 3e3e17fd6..ca2ae99cf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -51,7 +51,7 @@ resources :block_statistics, only: :show ## TODO: unused route resources :epoch_statistics, only: :show resources :market_data, only: :show - resources :udts, only: %i(index show) do + resources :udts, only: %i(index show update) do collection do get :download_csv end @@ -60,6 +60,7 @@ resources :address_udt_transactions, only: :show resources :distribution_data, only: :show resources :monetary_data, only: :show + resources :udt_verifications, only: :update end end draw "v2" diff --git a/db/migrate/20230913091025_create_udt_verifications.rb b/db/migrate/20230913091025_create_udt_verifications.rb new file mode 100644 index 000000000..5441b35b9 --- /dev/null +++ b/db/migrate/20230913091025_create_udt_verifications.rb @@ -0,0 +1,14 @@ +class CreateUdtVerifications < ActiveRecord::Migration[7.0] + def change + create_table :udt_verifications do |t| + t.integer :token + t.datetime :sent_at + t.inet :last_ip + t.belongs_to :udt + t.integer :udt_type_hash + + t.timestamps + t.index :udt_type_hash, unique: true + end + end +end diff --git a/db/migrate/20230914120928_change_contract_info_to_email_in_udt.rb b/db/migrate/20230914120928_change_contract_info_to_email_in_udt.rb new file mode 100644 index 000000000..163bcb6f1 --- /dev/null +++ b/db/migrate/20230914120928_change_contract_info_to_email_in_udt.rb @@ -0,0 +1,5 @@ +class ChangeContractInfoToEmailInUdt < ActiveRecord::Migration[7.0] + def change + rename_column :udts, :contact_info, :email + end +end diff --git a/db/migrate/20230918033957_add_statistics_to_contract.rb b/db/migrate/20230918033957_add_statistics_to_contract.rb new file mode 100644 index 000000000..e78c6eed2 --- /dev/null +++ b/db/migrate/20230918033957_add_statistics_to_contract.rb @@ -0,0 +1,9 @@ +class AddStatisticsToContract < ActiveRecord::Migration[7.0] + def change + add_column :contracts, :ckb_transactions_count, :decimal, precision: 30, default: 0 + add_column :contracts, :deployed_cells_count, :decimal, precision: 30, default: 0 + add_column :contracts, :referring_cells_count, :decimal, precision: 30, default: 0 + add_column :contracts, :total_deployed_cells_capacity, :decimal, precision: 30, default: 0 + add_column :contracts, :total_referring_cells_capacity, :decimal, precision: 30, default: 0 + end +end diff --git a/db/structure.sql b/db/structure.sql index 2d66ef4b6..3d2eae987 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1048,7 +1048,12 @@ CREATE TABLE public.contracts ( verified boolean DEFAULT false, created_at timestamp(6) without time zone NOT NULL, updated_at timestamp(6) without time zone NOT NULL, - deprecated boolean + deprecated boolean, + ckb_transactions_count numeric(30,0) DEFAULT 0.0, + deployed_cells_count numeric(30,0) DEFAULT 0.0, + referring_cells_count numeric(30,0) DEFAULT 0.0, + total_deployed_cells_capacity numeric(30,0) DEFAULT 0.0, + total_referring_cells_capacity numeric(30,0) DEFAULT 0.0 ); @@ -2195,6 +2200,41 @@ CREATE TABLE public.udt_transactions ( ); +-- +-- Name: udt_verifications; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.udt_verifications ( + id bigint NOT NULL, + token integer, + sent_at timestamp(6) without time zone, + last_ip inet, + udt_id bigint, + udt_type_hash integer, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: udt_verifications_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.udt_verifications_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: udt_verifications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.udt_verifications_id_seq OWNED BY public.udt_verifications.id; + + -- -- Name: udts; Type: TABLE; Schema: public; Owner: - -- @@ -2224,7 +2264,7 @@ CREATE TABLE public.udts ( display_name character varying, uan character varying, h24_ckb_transactions_count bigint DEFAULT 0, - contact_info character varying + email character varying ); @@ -2638,6 +2678,13 @@ ALTER TABLE ONLY public.type_scripts ALTER COLUMN id SET DEFAULT nextval('public ALTER TABLE ONLY public.udt_accounts ALTER COLUMN id SET DEFAULT nextval('public.udt_accounts_id_seq'::regclass); +-- +-- Name: udt_verifications id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.udt_verifications ALTER COLUMN id SET DEFAULT nextval('public.udt_verifications_id_seq'::regclass); + + -- -- Name: udts id; Type: DEFAULT; Schema: public; Owner: - -- @@ -3091,6 +3138,14 @@ ALTER TABLE ONLY public.udt_accounts ADD CONSTRAINT udt_accounts_pkey PRIMARY KEY (id); +-- +-- Name: udt_verifications udt_verifications_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.udt_verifications + ADD CONSTRAINT udt_verifications_pkey PRIMARY KEY (id); + + -- -- Name: udts udts_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -4127,6 +4182,20 @@ CREATE INDEX index_udt_transactions_on_ckb_transaction_id ON public.udt_transact CREATE INDEX index_udt_transactions_on_udt_id ON public.udt_transactions USING btree (udt_id); +-- +-- Name: index_udt_verifications_on_udt_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_udt_verifications_on_udt_id ON public.udt_verifications USING btree (udt_id); + + +-- +-- Name: index_udt_verifications_on_udt_type_hash; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_udt_verifications_on_udt_type_hash ON public.udt_verifications USING btree (udt_type_hash); + + -- -- Name: index_udts_on_type_hash; Type: INDEX; Schema: public; Owner: - -- @@ -4684,6 +4753,9 @@ INSERT INTO "schema_migrations" (version) VALUES ('20230711040233'), ('20230802015907'), ('20230808020637'), -('20230829061910'); +('20230829061910'), +('20230913091025'), +('20230914120928'), +('20230918033957'); diff --git a/lib/scheduler.rb b/lib/scheduler.rb index f440a9937..ff8d0fa58 100644 --- a/lib/scheduler.rb +++ b/lib/scheduler.rb @@ -121,4 +121,8 @@ def call_worker(clz) call_worker CleanAddressBlockSnapshotWorker end +s.every "1h", overlap: false do + call_worker ContractStatisticWorker +end + s.join diff --git a/test/controllers/api/v1/udt_verifications_controller_test.rb b/test/controllers/api/v1/udt_verifications_controller_test.rb new file mode 100644 index 000000000..18de08a91 --- /dev/null +++ b/test/controllers/api/v1/udt_verifications_controller_test.rb @@ -0,0 +1,47 @@ +require "test_helper" + +module Api + module V1 + class UdtVerificationsControllerTest < ActionDispatch::IntegrationTest + test "raise error when udt not exist" do + valid_put api_v1_udt_verification_url("0x#{SecureRandom.hex(32)}") + + assert_equal 404, response.status + assert_equal [{ "title" => "UDT Not Found", "detail" => "No UDT records found by given type hash", "code" => 1026, "status" => 404 }], + JSON.parse(response.body) + end + + test "raise error when udt no contact mail" do + udt = create(:udt, published: true) + valid_put api_v1_udt_verification_url(udt.type_hash) + + assert_equal 400, response.status + assert_equal [{ "title" => "UDT has no contact email", "detail" => "", "code" => 1033, "status" => 400 }], + JSON.parse(response.body) + end + + test "raise error when sent too frequently" do + udt = create(:udt, published: true, email: "example@sudt.com") + create(:udt_verification, udt: udt, udt_type_hash: udt.type_hash) + valid_put api_v1_udt_verification_url(udt.type_hash) + + assert_equal 400, response.status + assert_equal [{ "title" => "Token sent too frequently", "detail" => "", "code" => 1036, "status" => 400 }], + JSON.parse(response.body) + end + + test "should sent successfully" do + udt = create(:udt, published: true, email: "example@sudt.com") + valid_put api_v1_udt_verification_url(udt.type_hash) + + assert_equal 200, response.status + assert_equal "ok", JSON.parse(response.body) + uv = UdtVerification.first + assert_not_nil uv.token + assert_not_nil uv.sent_at + assert_not_nil uv.last_ip + assert_equal ActiveJob::Base.queue_adapter.enqueued_jobs[0][:args][0], "UdtVerificationMailer" + end + end + end +end diff --git a/test/controllers/api/v1/udts_controller_test.rb b/test/controllers/api/v1/udts_controller_test.rb index 052bfc56a..f833acfb4 100644 --- a/test/controllers/api/v1/udts_controller_test.rb +++ b/test/controllers/api/v1/udts_controller_test.rb @@ -415,73 +415,153 @@ class UdtsControllerTest < ActionDispatch::IntegrationTest assert_response :success end - # test "should update udt info suceessfully" do - # udt = create(:udt, published: true) - - # valid_put api_v1_udt_url(udt.type_hash), params: { - # symbol: "GWK", - # full_name: "GodwokenToken on testnet_v1", - # decimal: "8", - # total_amount: "100000000000", - # description: "The sUDT_ERC20_Proxy of Godwoken Test Token.", - # operator_website: "https://udt.coin", - # icon_file: "https://img.udt.img", - # uan: "GWK.gw|gb.ckb", - # display_name: "GodwokenToken (via Godwoken Bridge from CKB)", - # contact_info: "contact@usdt.com" - # } - - # assert_response :success - # udt.reload - # assert_equal udt.symbol, "GWK" - # assert_equal udt.full_name, "GodwokenToken on testnet_v1" - # assert_equal udt.decimal, 8 - # assert_equal udt.total_amount, 100000000000 - # assert_equal udt.description, "The sUDT_ERC20_Proxy of Godwoken Test Token." - # assert_equal udt.operator_website, "https://udt.coin" - # assert_equal udt.icon_file, "https://img.udt.img" - # assert_equal udt.uan, "GWK.gw|gb.ckb" - # assert_equal udt.display_name, "GodwokenToken (via Godwoken Bridge from CKB)" - # assert_equal udt.contact_info, "contact@usdt.com" - - # end - - # test "raise parameters error when update udt" do - # udt = create(:udt, published: true) - - # valid_put api_v1_udt_url(udt.type_hash), params: { - # symbol: "GWK", - # full_name: "GodwokenToken on testnet_v1", - # decimal: "8", - # description: "The sUDT_ERC20_Proxy of Godwoken Test Token.", - # operator_website: "https://udt.coin", - # icon_file: "https://img.udt.img", - # uan: "GWK.gw|gb.ckb", - # display_name: "GodwokenToken (via Godwoken Bridge from CKB)" - # } - - # assert_equal 400, response.status - # assert_equal response.body, "[{\"title\":\"UDT info parameters invalid\",\"detail\":\"Validation failed: Total amount can't be blank, Total amount is not a number\",\"code\":1030,\"status\":400}]" - # end - - # test "raise not found error when update udt" do - # udt = create(:udt, published: true) - - # valid_put api_v1_udt_url("#{udt.type_hash}0"), params: { - # symbol: "GWK", - # full_name: "GodwokenToken on testnet_v1", - # decimal: "8", - # total_amount: "100000000", - # description: "The sUDT_ERC20_Proxy of Godwoken Test Token.", - # operator_website: "https://udt.coin", - # icon_file: "https://img.udt.img", - # uan: "GWK.gw|gb.ckb", - # display_name: "GodwokenToken (via Godwoken Bridge from CKB)" - # } - - # assert_equal 404, response.status - # assert_equal response.body, "[{\"title\":\"UDT Not Found\",\"detail\":\"No UDT records found by given type hash\",\"code\":1026,\"status\":404}]" - # end + test "should submit udt info suceessfully" do + udt = create(:udt, published: true) + + valid_put api_v1_udt_url(udt.type_hash), params: { + symbol: "GWK", + full_name: "GodwokenToken on testnet_v1", + decimal: "8", + total_amount: "100000000000", + description: "The sUDT_ERC20_Proxy of Godwoken Test Token.", + operator_website: "https://udt.coin", + icon_file: "https://img.udt.img", + uan: "GWK.gw|gb.ckb", + display_name: "GodwokenToken (via Godwoken Bridge from CKB)", + email: "contact@usdt.com" + } + + assert_response :success + udt.reload + assert_equal udt.symbol, "GWK" + assert_equal udt.full_name, "GodwokenToken on testnet_v1" + assert_equal udt.decimal, 8 + assert_equal udt.total_amount, 100000000000 + assert_equal udt.description, "The sUDT_ERC20_Proxy of Godwoken Test Token." + assert_equal udt.operator_website, "https://udt.coin" + assert_equal udt.icon_file, "https://img.udt.img" + assert_equal udt.uan, "GWK.gw|gb.ckb" + assert_equal udt.display_name, "GodwokenToken (via Godwoken Bridge from CKB)" + assert_equal udt.email, "contact@usdt.com" + end + + test "raise email blank error when submit udt" do + udt = create(:udt, published: true) + + valid_put api_v1_udt_url(udt.type_hash), params: { + symbol: "GWK", + full_name: "GodwokenToken on testnet_v1", + decimal: "8", + description: "The sUDT_ERC20_Proxy of Godwoken Test Token.", + operator_website: "https://udt.coin", + icon_file: "https://img.udt.img", + uan: "GWK.gw|gb.ckb", + display_name: "GodwokenToken (via Godwoken Bridge from CKB)" + } + + assert_equal 400, response.status + assert_equal [{ "title" => "UDT info parameters invalid", "detail" => "Email can't be blank", "code" => 1030, "status" => 400 }], + JSON.parse(response.body) + end + + test "raise email format error when submit udt" do + udt = create(:udt, published: true) + + valid_put api_v1_udt_url(udt.type_hash), params: { + symbol: "GWK", + full_name: "GodwokenToken on testnet_v1", + decimal: "8", + total_amount: "100000000000", + description: "The sUDT_ERC20_Proxy of Godwoken Test Token.", + operator_website: "https://udt.coin", + icon_file: "https://img.udt.img", + uan: "GWK.gw|gb.ckb", + display_name: "GodwokenToken (via Godwoken Bridge from CKB)", + email: "abcdefg" + } + + assert_equal 400, response.status + assert_equal [{ "title" => "UDT info parameters invalid", "detail" => "Validation failed: Email Not a valid email", "code" => 1030, "status" => 400 }], + JSON.parse(response.body) + end + + test "raise not found error when submit udt" do + udt = create(:udt, published: true) + + valid_put api_v1_udt_url("#{udt.type_hash}0"), params: { + symbol: "GWK", + full_name: "GodwokenToken on testnet_v1", + decimal: "8", + total_amount: "100000000", + description: "The sUDT_ERC20_Proxy of Godwoken Test Token.", + operator_website: "https://udt.coin", + icon_file: "https://img.udt.img", + uan: "GWK.gw|gb.ckb", + display_name: "GodwokenToken (via Godwoken Bridge from CKB)" + } + + assert_equal 404, response.status + assert_equal [{ "title" => "UDT Not Found", "detail" => "No UDT records found by given type hash", "code" => 1026, "status" => 404 }], + JSON.parse(response.body) + end + + test "raise no udt_verification error when update udt" do + udt = create(:udt, email: "abc@sudt.com", published: true) + valid_put api_v1_udt_url("#{udt.type_hash}"), params: { + symbol: "GWK", + full_name: "GodwokenToken on testnet_v1", + token: "123456" + } + + assert_equal 404, response.status + assert_equal [{ "title" => "UDT Verification Not Found", "detail" => "No UDT verification records found by given type hash", "code" => 1032, "status" => 404 }], + JSON.parse(response.body) + end + + test "raise udt_verification expired error when update udt" do + udt = create(:udt, email: "abc@sudt.com", published: true) + create(:udt_verification, sent_at: Time.now - 11.minutes, udt: udt) + valid_put api_v1_udt_url("#{udt.type_hash}"), params: { + symbol: "GWK", + full_name: "GodwokenToken on testnet_v1", + total_amount: "100000000", + token: "123456" + } + + assert_equal 400, response.status + assert_equal [{ "title" => "Token has expired", "detail" => "", "code" => 1034, "status" => 400 }], + JSON.parse(response.body) + end + + test "raise udt_verification token not match error when update udt" do + udt = create(:udt, email: "abc@sudt.com", published: true) + create(:udt_verification, udt: udt) + valid_put api_v1_udt_url("#{udt.type_hash}"), params: { + symbol: "GWK", + full_name: "GodwokenToken on testnet_v1", + total_amount: "100000000", + token: "123" + } + + assert_equal 400, response.status + assert_equal [{ "title" => "Token is not matched", "detail" => "", "code" => 1035, "status" => 400 }], + JSON.parse(response.body) + end + + test "should update successfully when update udt" do + udt = create(:udt, email: "abc@sudt.com", published: true) + create(:udt_verification, udt: udt) + valid_put api_v1_udt_url("#{udt.type_hash}"), params: { + symbol: "GWK", + full_name: "GodwokenToken on testnet_v1", + total_amount: "100000000", + token: "123456" + } + + assert_equal 200, response.status + assert_equal "ok", JSON.parse(response.body) + assert_equal "GWK", udt.reload.symbol + end end end end diff --git a/test/factories/udt_verifications.rb b/test/factories/udt_verifications.rb new file mode 100644 index 000000000..faeba6e38 --- /dev/null +++ b/test/factories/udt_verifications.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :udt_verification do + token { 123456 } + sent_at { Time.now } + last_ip { "127.0.0.1" } + udt_type_hash { "0x#{SecureRandom.hex(32)}" } + udt + end +end diff --git a/test/fixtures/udt_verification_mailer/send_token_email.en.text.erb b/test/fixtures/udt_verification_mailer/send_token_email.en.text.erb new file mode 100644 index 000000000..166d98fcf --- /dev/null +++ b/test/fixtures/udt_verification_mailer/send_token_email.en.text.erb @@ -0,0 +1,12 @@ +Dear Token Info Author, + +To ensure the effectiveness of your Token Info modification operation, we have generated a verification code for you to complete the Token Info update. + +Your verification code is: 123456 + +Please note that this verification code is only valid for the next 10 minutes. Please use it to complete your Token Info modification within this time frame and refrain from sharing your code with others. If you have not initiated any action or if this operation is not associated with you, please disregard this email. + +If you have any questions or require assistance, please feel free to contact our customer support team.Thank you for your trust and support! + +Best regards, +The MagicKBase Team diff --git a/test/fixtures/udt_verification_mailer/send_token_email.zh_CN.text.erb b/test/fixtures/udt_verification_mailer/send_token_email.zh_CN.text.erb new file mode 100644 index 000000000..eba324bd8 --- /dev/null +++ b/test/fixtures/udt_verification_mailer/send_token_email.zh_CN.text.erb @@ -0,0 +1,10 @@ +尊敬的 Token Info 作者, + +为了确保本次 Token Info 修改操作的有效性,我们为您生成了一个验证码,用于完成 Token Info 的修改操作。 + +您的验证码是:123456 + +请注意,该验证码仅在接下来的10分钟内有效。请在此时间内完成 Token Info 的修改操作,并且请不要分享您的验证码给他人。如果您没有进行任何操作或者不是您的操作,请忽略此邮件。 + +如果您有任何疑问或需要帮助,请随时联系我们的客户支持团队。谢谢您的信任和支持! +MagicKBase 团队 diff --git a/test/mailers/previews/udt_verification_mailer_preview.rb b/test/mailers/previews/udt_verification_mailer_preview.rb new file mode 100644 index 000000000..9f5a6cee4 --- /dev/null +++ b/test/mailers/previews/udt_verification_mailer_preview.rb @@ -0,0 +1,5 @@ +class UdtVerificationMailerPreview < ActionMailer::Preview + def send_token + UdtVerificationMailer.with(email: "receiver@example.com", token: "123456", locale: params[:locale]).send_token + end +end diff --git a/test/mailers/udt_verification_mailer_test.rb b/test/mailers/udt_verification_mailer_test.rb new file mode 100644 index 000000000..0193055cc --- /dev/null +++ b/test/mailers/udt_verification_mailer_test.rb @@ -0,0 +1,27 @@ +require "test_helper" + +class UdtVerificationMailerTest < ActionMailer::TestCase + test "send token" do + email = UdtVerificationMailer.with(email: "receiver@example.com", token: "123456").send_token + + assert_emails 1 do + email.deliver_now + end + + # Test the body of the sent email contains what we expect it to + assert_equal ["noreply@magickbase.com"], email.from + assert_equal ["receiver@example.com"], email.to + assert_equal "Token Info Verification", email.subject + assert_equal "#{read_fixture('send_token_email.en.text.erb').join}\n", email.body.to_s + end + + test "when zh_CN locale" do + email = UdtVerificationMailer.with(email: "receiver@example.com", token: "123456", locale: "zh_CN").send_token + + assert_emails 1 do + email.deliver_now + end + + assert_equal "#{read_fixture('send_token_email.zh_CN.text.erb').join}\n", email.body.to_s.tr("\r", "") + end +end diff --git a/test/models/udt_verification_test.rb b/test/models/udt_verification_test.rb new file mode 100644 index 000000000..0527a4ee2 --- /dev/null +++ b/test/models/udt_verification_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class UdtVerificationTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end