Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Reissuable Token AR #52

Merged
merged 15 commits into from
May 27, 2021
26 changes: 26 additions & 0 deletions lib/generators/glueby/contract/reissuable_token_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Glueby
module Contract
class ReissuableTokenGenerator < Rails::Generators::Base
include ::Rails::Generators::Migration
include Glueby::Generator::MigrateGenerator
extend Glueby::Generator::MigrateGenerator::ClassMethod

source_root File.expand_path('templates', __dir__)

def create_migration_file
migration_dir = File.expand_path("db/migrate")

if self.class.migration_exists?(migration_dir, "create_reissuable_token")
::Kernel.warn "Migration already exists: create_reissuable_token"
else
migration_template(
"reissuable_token_table.rb.erb",
"db/migrate/create_reissuable_token.rb",
migration_version: migration_version,
table_options: table_options,
)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CreateReissuableToken < ActiveRecord::Migration<%= migration_version %>
def change
create_table :reissuable_tokens<%= table_options %> do |t|
t.string :color_id
rantan marked this conversation as resolved.
Show resolved Hide resolved
t.string :script_pubkey
rantan marked this conversation as resolved.
Show resolved Hide resolved
t.timestamps
end
add_index :reissuable_tokens, [:color_id], unique: true
end
end
1 change: 1 addition & 0 deletions lib/glueby/contract/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module Glueby
module Contract
module AR
autoload :ReissuableToken, 'glueby/contract/active_record/reissuable_token'
autoload :Timestamp, 'glueby/contract/active_record/timestamp'
end
end
Expand Down
26 changes: 26 additions & 0 deletions lib/glueby/contract/active_record/reissuable_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Glueby
module Contract
module AR
class ReissuableToken < ::ActiveRecord::Base

# Get the script_pubkey corresponding to the color_id in Tapyrus::Script format
# @param [String] color_id
# @return [Tapyrus::Script]
def self.script_pubkey(color_id)
script_pubkey = Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).pluck(:script_pubkey).first
if script_pubkey
Tapyrus::Script.parse_from_payload(script_pubkey.htb)
end
end

# Check if the color_id is already stored
# @param [String] color_id
# @return [Boolean]
def self.saved?(color_id)
Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).exists?
end

end
end
end
end
41 changes: 29 additions & 12 deletions lib/glueby/contract/token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount:
raise Glueby::Contract::Errors::UnsupportedTokenType
end
txs.each { |tx| issuer.internal_wallet.broadcast(tx) }
[new(color_id: color_id, script_pubkey: script_pubkey), txs]
if token_type == Tapyrus::Color::TokenTypes::REISSUABLE
Glueby::Contract::AR::ReissuableToken.create(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
rantan marked this conversation as resolved.
Show resolved Hide resolved
end
[new(color_id: color_id), txs]
end

private
Expand Down Expand Up @@ -116,13 +119,16 @@ def issue_nft_token(issuer:)
def reissue!(issuer:, amount:)
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
raise Glueby::Contract::Errors::InvalidTokenType unless token_type == Tapyrus::Color::TokenTypes::REISSUABLE
raise Glueby::Contract::Errors::UnknownScriptPubkey unless @script_pubkey

estimated_fee = FixedFeeEstimator.new.fee(Tapyrus::Tx.new)
funding_tx = create_funding_tx(wallet: issuer, amount: estimated_fee, script: @script_pubkey)
tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
[funding_tx, tx].each { |tx| issuer.internal_wallet.broadcast(tx) }
[color_id, tx]
if script_pubkey.present?
rantan marked this conversation as resolved.
Show resolved Hide resolved
estimated_fee = FixedFeeEstimator.new.fee(Tapyrus::Tx.new)
funding_tx = create_funding_tx(wallet: issuer, amount: estimated_fee, script: script_pubkey)
tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
[funding_tx, tx].each { |tx| issuer.internal_wallet.broadcast(tx) }
[color_id, tx]
else
raise Glueby::Contract::Errors::UnknownScriptPubkey unless script_pubkey
rantan marked this conversation as resolved.
Show resolved Hide resolved
end
end

# Send the token to other wallet
Expand Down Expand Up @@ -173,12 +179,19 @@ def token_type
color_id.type
end

# Return the script_pubkey of the token from ActiveRecord
# @return [String] script_pubkey
def script_pubkey
Glueby::Contract::AR::ReissuableToken.script_pubkey(@color_id.to_hex)
rantan marked this conversation as resolved.
Show resolved Hide resolved
end

# Return serialized payload
# @return [String] payload
def to_payload
payload = +''
payload << @color_id.to_payload
payload << @script_pubkey.to_payload if @script_pubkey
payload << script_pubkey.to_payload if script_pubkey.present?
rantan marked this conversation as resolved.
Show resolved Hide resolved
payload
end

# Restore token from payload
Expand All @@ -188,13 +201,17 @@ def self.parse_from_payload(payload)
color_id, script_pubkey = payload.unpack('a33a*')
color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(color_id)
script_pubkey = Tapyrus::Script.parse_from_payload(script_pubkey) if script_pubkey
new(color_id: color_id, script_pubkey: script_pubkey)
if !Glueby::Contract::AR::ReissuableToken.saved?(color_id.to_hex) && script_pubkey.present?
rantan marked this conversation as resolved.
Show resolved Hide resolved
Glueby::Contract::AR::ReissuableToken.create(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
rantan marked this conversation as resolved.
Show resolved Hide resolved
end
new(color_id: color_id)
end

def initialize(color_id:, script_pubkey:nil)
# Generate Token Instance
# @param color_id [String]
def initialize(color_id:)
@color_id = color_id
@script_pubkey = script_pubkey
end
end
end
end
end
38 changes: 29 additions & 9 deletions spec/glueby/contract/token_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

RSpec.describe 'Glueby::Contract::Token' do
RSpec.describe 'Glueby::Contract::Token', active_record: true do
let(:wallet) { TestWallet.new(internal_wallet) }
let(:internal_wallet) { TestInternalWallet.new }
let(:unspents) do
Expand Down Expand Up @@ -69,12 +69,26 @@
let(:token_type) { Tapyrus::Color::TokenTypes::REISSUABLE }
let(:amount) { 1_000 }

it {
expect {subject}.not_to raise_error
expect(subject[0].color_id.type).to eq Tapyrus::Color::TokenTypes::REISSUABLE
expect(subject[0].color_id.valid?).to be true
expect(subject[1][1].valid?).to be true
}
context 'reissuable token' do
it do
expect {subject}.not_to raise_error
expect(subject[0].color_id.type).to eq Tapyrus::Color::TokenTypes::REISSUABLE
expect(subject[0].color_id.valid?).to be true
expect(subject[1][1].valid?).to be true
expect(Glueby::Contract::AR::ReissuableToken.count).to eq 1
rantan marked this conversation as resolved.
Show resolved Hide resolved
end
end

context 'non reissuable token' do
let(:token_type) { Tapyrus::Color::TokenTypes::NON_REISSUABLE }
it do
expect {subject}.not_to raise_error
expect(subject[0].color_id.type).to eq Tapyrus::Color::TokenTypes::NON_REISSUABLE
expect(subject[0].color_id.valid?).to be true
expect(subject[1][0].valid?).to be true
expect(Glueby::Contract::AR::ReissuableToken.count).to eq 0
end
end

context 'invalid amount' do
let(:amount) { 0 }
Expand Down Expand Up @@ -281,12 +295,18 @@

let(:token) { Glueby::Contract::Token.parse_from_payload('c150ad685ec8638543b2356cb1071cf834fb1c84f5fa3a71699c3ed7167dfcdbb376a914234113b860822e68f9715d1957af28b8f5117ee288ac'.htb) }

it { is_expected.to eq 'c150ad685ec8638543b2356cb1071cf834fb1c84f5fa3a71699c3ed7167dfcdbb376a914234113b860822e68f9715d1957af28b8f5117ee288ac' }
it do
expect(subject).to eq 'c150ad685ec8638543b2356cb1071cf834fb1c84f5fa3a71699c3ed7167dfcdbb376a914234113b860822e68f9715d1957af28b8f5117ee288ac'
expect(Glueby::Contract::AR::ReissuableToken.count).to eq 1
end

context 'with no script pubkey' do
let(:token) { Glueby::Contract::Token.parse_from_payload('c150ad685ec8638543b2356cb1071cf834fb1c84f5fa3a71699c3ed7167dfcdbb3'.htb) }

it { is_expected.to eq 'c150ad685ec8638543b2356cb1071cf834fb1c84f5fa3a71699c3ed7167dfcdbb3' }
it do
expect(subject).to eq 'c150ad685ec8638543b2356cb1071cf834fb1c84f5fa3a71699c3ed7167dfcdbb3'
expect(Glueby::Contract::AR::ReissuableToken.count).to eq 0
end
end
end
end
8 changes: 8 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ def setup_database
end
connection.add_index :system_informations, [:info_key], unique: true
Glueby::AR::SystemInformation.create(info_key: 'synced_block_number', info_value: '0')

connection.create_table :reissuable_tokens, force: true do |t|
t.string :color_id
t.string :script_pubkey
t.timestamps
end
connection.add_index :reissuable_tokens, [:color_id], unique: true
end

def teardown_database
Expand All @@ -93,6 +100,7 @@ def teardown_database
connection.drop_table :keys, if_exists: true
connection.drop_table :timestamps, if_exists: true
connection.drop_table :system_informations, if_exists: true
connection.drop_table :reissuable_tokens, if_exists: true
end

class TestWallet
Expand Down