From ad56ab7d02b50ae93f6fa5eac486e855452258db Mon Sep 17 00:00:00 2001 From: emperorhan Date: Fri, 3 May 2019 00:57:41 +0900 Subject: [PATCH 1/7] feat: implement erc20 in eos contract --- eos/erctoken/ecrio.erctoken.cpp | 209 ++++++++++++++++++++++++++++++++ eos/erctoken/ecrio.erctoken.hpp | 97 +++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 eos/erctoken/ecrio.erctoken.cpp create mode 100644 eos/erctoken/ecrio.erctoken.hpp diff --git a/eos/erctoken/ecrio.erctoken.cpp b/eos/erctoken/ecrio.erctoken.cpp new file mode 100644 index 0000000..1fa0b4f --- /dev/null +++ b/eos/erctoken/ecrio.erctoken.cpp @@ -0,0 +1,209 @@ +#include "ecrio.erctoken.hpp" + +namespace eosio { +ACTION erctoken::issue( name to, asset quantity, string memo ) { + require_auth( get_self() ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity" ); + + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + + _stat_state.supply += quantity; + + add_balance( to, quantity, get_self() ); + + if( to != get_self() ) { + SEND_INLINE_ACTION( *this, transfer, { {get_self(), "active"_n} }, + { get_self(), to, quantity, memo } + ); + } +} + +ACTION erctoken::burn( name account, asset quantity, string memo ) { + require_auth( account ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must burn positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + sub_balance( account, quantity ); + _stat_state.supply -= quantity; +} + +ACTION erctoken::burnfrom( name burner, name owner, asset quantity, string memo ) { + require_auth( burner ); + check( burner != owner, "cannot burn self" ); + check( is_account( owner ), "owner account does not exist"); + + require_recipient( burner ); + require_recipient( owner ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must be positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + allowances allws( get_self(), owner.value ); + auto itr = allws.require_find( burner.value, "burner is not exist" ); + check( itr->balance.amount >= quantity.amount, "burner does not have enough allowed amount" ); + allws.modify( itr, same_payer, [&]( auto& a ) { + a.balance -= quantity; + }); + sub_balance( owner, quantity ); + _stat_state.supply -= quantity; +} + +ACTION erctoken::transfer( name from, + name to, + asset quantity, + string memo ) { + check( from != to, "cannot transfer to self" ); + require_auth( from ); + check( is_account( to ), "to account does not exist"); + + require_recipient( from ); + require_recipient( to ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must transfer positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + auto payer = has_auth( to ) ? to : from; + + sub_balance( from, quantity ); + add_balance( to, quantity, payer ); +} + +void erctoken::sub_balance( name owner, asset value ) { + accounts from_acnts( get_self(), owner.value ); + + const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" ); + check( from.balance.amount >= value.amount, "overdrawn balance" ); + + from_acnts.modify( from, owner, [&]( auto& a ) { + a.balance -= value; + }); +} + +void erctoken::add_balance( name owner, asset value, name ram_payer ) { + accounts to_acnts( get_self(), owner.value ); + auto to = to_acnts.find( value.symbol.code().raw() ); + if( to == to_acnts.end() ) { + to_acnts.emplace( ram_payer, [&]( auto& a ){ + a.balance = value; + }); + } else { + to_acnts.modify( to, same_payer, [&]( auto& a ) { + a.balance += value; + }); + } +} + +ACTION erctoken::approve( name owner, name spender, asset quantity ) { + require_auth( owner ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + + allowances allws( get_self(), owner.value ); + auto it = allws.find( spender.value ); + check( it == allws.end(), "spender already exist" ); + allws.emplace( owner, [&]( auto& a ){ + a.spender = spender; + a.balance = quantity; + }); +} + +ACTION erctoken::transferfrom( name spender, name from, name to, asset quantity, string memo ) { + require_auth( spender ); + check( from != to, "cannot transfer to self" ); + check( is_account( from ), "from account does not exist"); + check( is_account( to ), "to account does not exist"); + + require_recipient( from ); + require_recipient( to ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must transfer positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + allowances allws( get_self(), from.value ); + auto it = allws.require_find( spender.value, "spender is not exist" ); + check( it->balance.symbol == quantity.symbol, "symbol precision mismatch" ); + check( it->balance.amount >= quantity.amount, "spender does not have enough allowed amount" ); + + auto payer = has_auth( to ) ? to : spender; + + sub_balance( from, quantity ); + add_balance( to, quantity, payer ); + + allws.modify( it, same_payer, [&]( auto& a ){ + a.balance -= quantity; + }); +} + +ACTION erctoken::incallowance( name owner, name spender, asset quantity ) { + require_auth( owner ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + + allowances allws( get_self(), owner.value ); + auto it = allws.require_find( spender.value, "spender is not registed" ); + allws.modify( it, same_payer, [&]( auto& a ) { + a.balance += quantity; + }); +} + +ACTION erctoken::decallowance( name owner, name spender, asset quantity ) { + require_auth( owner ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + + allowances allws( get_self(), owner.value ); + auto it = allws.require_find( spender.value, "spender is not registed" ); + allws.modify( it, same_payer, [&]( auto& a ) { + a.balance -= quantity; + }); +} + +ACTION erctoken::open( name owner, const symbol& symbol, name ram_payer ) { + require_auth( ram_payer ); + + auto sym_code_raw = symbol.code().raw(); + + check( _stat_state.supply.symbol == symbol, "symbol precision mismatch" ); + + accounts acnts( get_self(), owner.value ); + auto it = acnts.find( sym_code_raw ); + if( it == acnts.end() ) { + acnts.emplace( ram_payer, [&]( auto& a ){ + a.balance = asset{0, symbol}; + }); + } +} + +ACTION erctoken::close( name owner, const symbol& symbol ) { + require_auth( owner ); + accounts acnts( get_self(), owner.value ); + auto it = acnts.find( symbol.code().raw() ); + check( it != acnts.end(), "Balance row already deleted or never existed. Action won't have any effect." ); + check( it->balance.amount == 0, "Cannot close because the balance is not zero." ); + acnts.erase( it ); +} + +} // namespace eosio + +EOSIO_DISPATCH( eosio::erctoken, (issue)(burn)(burnfrom)(transfer)(approve)(transferfrom)(incallowance)(decallowance)(open)(close) ) diff --git a/eos/erctoken/ecrio.erctoken.hpp b/eos/erctoken/ecrio.erctoken.hpp new file mode 100644 index 0000000..f7d3749 --- /dev/null +++ b/eos/erctoken/ecrio.erctoken.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include + +#include + +using namespace eosio; + +namespace eosio{ + using std::string; + + CONTRACT erctoken : public contract { + public: + erctoken( name self, name first_receiver, datastream ds ) + : contract( self, first_receiver, ds ), _stat( self, self.value ) { + _stat_state = _stat.exists() ? _stat.get() : currency_stat{}; + } + + ~erctoken() { + _stat.set( _stat_state, get_self() ); + } + + ACTION issue( name to, asset quantity, string memo ); + + ACTION burn( name account, asset quantity, string memo ); + + ACTION burnfrom( name burner, name owner, asset quantity, string memo ); + + ACTION transfer( name from, name to, asset quantity, string memo ); + + ACTION approve( name owner, name spender, asset quantity ); + + ACTION transferfrom( name spender, name from, name to, asset quantity, string memo ); + + ACTION incallowance( name owner, name spender, asset quantity ); + + ACTION decallowance( name owner, name spender, asset quantity ); + + ACTION open( name owner, const symbol& symbol, name ram_payer ); + + ACTION close( name owner, const symbol& symbol ); + + static asset get_supply( name token_contract_account ){ + stat_singleton _stat( token_contract_account, token_contract_account.value ); + const auto& st = _stat.get(); + return st.supply; + } + + static asset get_balance( name token_contract_account, name owner, symbol_code sym_code ){ + accounts accountstable( token_contract_account, owner.value ); + const auto& ac = accountstable.get( sym_code.raw() ); + return ac.balance; + } + + using issue_action = eosio::action_wrapper<"issue"_n, &erctoken::issue>; + using burn_action = eosio::action_wrapper<"burn"_n, &erctoken::burn>; + using burnfrom_action = eosio::action_wrapper<"burnfrom"_n, &erctoken::burnfrom>; + using transfer_action = eosio::action_wrapper<"transfer"_n, &erctoken::transfer>; + using approve_action = eosio::action_wrapper<"approve"_n, &erctoken::approve>; + using transferfrom_action = eosio::action_wrapper<"transferfrom"_n, &erctoken::transferfrom>; + using incallowance_action = eosio::action_wrapper<"incallowance"_n, &erctoken::incallowance>; + using decallowance_action = eosio::action_wrapper<"decallowance"_n, &erctoken::decallowance>; + using open_action = eosio::action_wrapper<"open"_n, &erctoken::open>; + using close_action = eosio::action_wrapper<"close"_n, &erctoken::close>; + + private: + TABLE account { + asset balance; + uint64_t primary_key()const { return balance.symbol.code().raw(); } + }; + + struct [[eosio::table("stat")]] currency_stat { + asset supply = asset{0, symbol(symbol_code("TEST"), 5)}; + }; + + TABLE allowance { + name spender; + asset balance; + + uint64_t primary_key()const { return spender.value; } + }; + + typedef eosio::multi_index< "accounts"_n, account > accounts; + typedef eosio::multi_index< "allowances"_n, allowance > allowances; + typedef eosio::singleton< "stat"_n, currency_stat > stat_singleton; + + stat_singleton _stat; + + currency_stat _stat_state; + + void sub_balance( name owner, asset value ); + void add_balance( name owner, asset value, name ram_payer ); + }; + +} // namespace eosio From 8184e561e38b154aaf027c0a5413c41d9d1b3741 Mon Sep 17 00:00:00 2001 From: emperorhan Date: Fri, 3 May 2019 01:33:26 +0900 Subject: [PATCH 2/7] feat: fix ram payer issue --- eos/erctoken/ecrio.erctoken.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/eos/erctoken/ecrio.erctoken.cpp b/eos/erctoken/ecrio.erctoken.cpp index 1fa0b4f..ea403bb 100644 --- a/eos/erctoken/ecrio.erctoken.cpp +++ b/eos/erctoken/ecrio.erctoken.cpp @@ -55,7 +55,15 @@ ACTION erctoken::burnfrom( name burner, name owner, asset quantity, string memo allws.modify( itr, same_payer, [&]( auto& a ) { a.balance -= quantity; }); - sub_balance( owner, quantity ); + // sub_balance( owner, quantity ); + accounts from_acnts( get_self(), owner.value ); + const auto& from = from_acnts.get( quantity.symbol.code().raw(), "no balance object found" ); + check( from.balance.amount >= quantity.amount, "overdrawn balance" ); + + from_acnts.modify( from, burner, [&]( auto& a ) { + a.balance -= quantity; + }); + _stat_state.supply -= quantity; } @@ -143,7 +151,14 @@ ACTION erctoken::transferfrom( name spender, name from, name to, asset quantity, auto payer = has_auth( to ) ? to : spender; - sub_balance( from, quantity ); + accounts from_acnts( get_self(), from.value ); + const auto& facnt = from_acnts.get( quantity.symbol.code().raw(), "no balance object found" ); + check( facnt.balance.amount >= quantity.amount, "overdrawn balance" ); + + from_acnts.modify( facnt, spender, [&]( auto& a ) { + a.balance -= quantity; + }); + // sub_balance( from, quantity ); add_balance( to, quantity, payer ); allws.modify( it, same_payer, [&]( auto& a ){ From a0375d22b647cf534fb707a83828ce2b99752e79 Mon Sep 17 00:00:00 2001 From: emperorhan Date: Fri, 3 May 2019 01:42:57 +0900 Subject: [PATCH 3/7] feat: Resolve issues with negative occurrences --- eos/erctoken/ecrio.erctoken.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eos/erctoken/ecrio.erctoken.cpp b/eos/erctoken/ecrio.erctoken.cpp index ea403bb..2f09090 100644 --- a/eos/erctoken/ecrio.erctoken.cpp +++ b/eos/erctoken/ecrio.erctoken.cpp @@ -189,6 +189,8 @@ ACTION erctoken::decallowance( name owner, name spender, asset quantity ) { allowances allws( get_self(), owner.value ); auto it = allws.require_find( spender.value, "spender is not registed" ); + check( it->balance >= quantity, "there is not enough balance" ); + allws.modify( it, same_payer, [&]( auto& a ) { a.balance -= quantity; }); From f5ac59a812b307c31a5af8ada15c54f146a26061 Mon Sep 17 00:00:00 2001 From: emperorhan Date: Wed, 8 May 2019 09:34:06 +0900 Subject: [PATCH 4/7] feat: add vscode --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5358247..84b25e2 100644 --- a/.gitignore +++ b/.gitignore @@ -126,4 +126,7 @@ Icon .AppleDesktop Network Trash Folder Temporary Items -.apdisk \ No newline at end of file +.apdisk + +#vscode +.vscode From 943ef89587eb7abf0421ccda046711880bb256d4 Mon Sep 17 00:00:00 2001 From: emperorhan Date: Wed, 8 May 2019 09:35:23 +0900 Subject: [PATCH 5/7] feat: add copyright --- eos/erctoken/ecrio.erctoken.cpp | 16 ++++++++++++++++ eos/erctoken/ecrio.erctoken.hpp | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/eos/erctoken/ecrio.erctoken.cpp b/eos/erctoken/ecrio.erctoken.cpp index 2f09090..77433ec 100644 --- a/eos/erctoken/ecrio.erctoken.cpp +++ b/eos/erctoken/ecrio.erctoken.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2019 DE-labtory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "ecrio.erctoken.hpp" namespace eosio { diff --git a/eos/erctoken/ecrio.erctoken.hpp b/eos/erctoken/ecrio.erctoken.hpp index f7d3749..86594d2 100644 --- a/eos/erctoken/ecrio.erctoken.hpp +++ b/eos/erctoken/ecrio.erctoken.hpp @@ -1,3 +1,19 @@ +/* + * Copyright 2019 DE-labtory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include From 8a62c3bb4e1cdb5381450821ab274f7fce698ebd Mon Sep 17 00:00:00 2001 From: emperorhan Date: Mon, 27 May 2019 15:43:34 +0900 Subject: [PATCH 6/7] Add: ERC20 for eos, ERC721 for eos --- .../ecrio.erctoken.cpp => erc20/erc20.cpp} | 62 ++--- .../ecrio.erctoken.hpp => erc20/erc20.hpp} | 30 +- eos/erc721/erc721.cpp | 257 ++++++++++++++++++ eos/erc721/erc721.hpp | 97 +++++++ 4 files changed, 393 insertions(+), 53 deletions(-) rename eos/{erctoken/ecrio.erctoken.cpp => erc20/erc20.cpp} (76%) rename eos/{erctoken/ecrio.erctoken.hpp => erc20/erc20.hpp} (88%) create mode 100644 eos/erc721/erc721.cpp create mode 100644 eos/erc721/erc721.hpp diff --git a/eos/erctoken/ecrio.erctoken.cpp b/eos/erc20/erc20.cpp similarity index 76% rename from eos/erctoken/ecrio.erctoken.cpp rename to eos/erc20/erc20.cpp index 77433ec..cfe4854 100644 --- a/eos/erctoken/ecrio.erctoken.cpp +++ b/eos/erc20/erc20.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ -#include "ecrio.erctoken.hpp" +#include "erc20.hpp" namespace eosio { -ACTION erctoken::issue( name to, asset quantity, string memo ) { +ACTION erc20::issue( name to, asset quantity, string memo ) { require_auth( get_self() ); auto sym = quantity.symbol; @@ -40,24 +40,23 @@ ACTION erctoken::issue( name to, asset quantity, string memo ) { } } -ACTION erctoken::burn( name account, asset quantity, string memo ) { - require_auth( account ); +ACTION erc20::burn( name owner, asset quantity, string memo ) { + require_auth( owner ); check( quantity.is_valid(), "invalid quantity" ); check( quantity.amount > 0, "must burn positive quantity" ); check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); check( memo.size() <= 256, "memo has more than 256 bytes" ); - sub_balance( account, quantity ); + sub_balance( owner, quantity ); _stat_state.supply -= quantity; } -ACTION erctoken::burnfrom( name burner, name owner, asset quantity, string memo ) { +ACTION erc20::burnfrom( name burner, name owner, asset quantity, string memo ) { require_auth( burner ); - check( burner != owner, "cannot burn self" ); + check( burner != owner, "cannot burnfrom self" ); check( is_account( owner ), "owner account does not exist"); - require_recipient( burner ); require_recipient( owner ); check( quantity.is_valid(), "invalid quantity" ); @@ -71,22 +70,16 @@ ACTION erctoken::burnfrom( name burner, name owner, asset quantity, string memo allws.modify( itr, same_payer, [&]( auto& a ) { a.balance -= quantity; }); - // sub_balance( owner, quantity ); - accounts from_acnts( get_self(), owner.value ); - const auto& from = from_acnts.get( quantity.symbol.code().raw(), "no balance object found" ); - check( from.balance.amount >= quantity.amount, "overdrawn balance" ); - from_acnts.modify( from, burner, [&]( auto& a ) { - a.balance -= quantity; - }); + sub_balance( owner, quantity ); _stat_state.supply -= quantity; } -ACTION erctoken::transfer( name from, - name to, - asset quantity, - string memo ) { +ACTION erc20::transfer( name from, + name to, + asset quantity, + string memo ) { check( from != to, "cannot transfer to self" ); require_auth( from ); check( is_account( to ), "to account does not exist"); @@ -105,18 +98,20 @@ ACTION erctoken::transfer( name from, add_balance( to, quantity, payer ); } -void erctoken::sub_balance( name owner, asset value ) { +void erc20::sub_balance( name owner, asset value ) { accounts from_acnts( get_self(), owner.value ); const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" ); check( from.balance.amount >= value.amount, "overdrawn balance" ); - from_acnts.modify( from, owner, [&]( auto& a ) { + name payer = !has_auth(owner) ? same_payer : owner; + + from_acnts.modify( from, payer, [&]( auto& a ) { a.balance -= value; }); } -void erctoken::add_balance( name owner, asset value, name ram_payer ) { +void erc20::add_balance( name owner, asset value, name ram_payer ) { accounts to_acnts( get_self(), owner.value ); auto to = to_acnts.find( value.symbol.code().raw() ); if( to == to_acnts.end() ) { @@ -130,7 +125,7 @@ void erctoken::add_balance( name owner, asset value, name ram_payer ) { } } -ACTION erctoken::approve( name owner, name spender, asset quantity ) { +ACTION erc20::approve( name owner, name spender, asset quantity ) { require_auth( owner ); check( quantity.is_valid(), "invalid quantity" ); @@ -146,7 +141,7 @@ ACTION erctoken::approve( name owner, name spender, asset quantity ) { }); } -ACTION erctoken::transferfrom( name spender, name from, name to, asset quantity, string memo ) { +ACTION erc20::transferfrom( name spender, name from, name to, asset quantity, string memo ) { require_auth( spender ); check( from != to, "cannot transfer to self" ); check( is_account( from ), "from account does not exist"); @@ -167,14 +162,7 @@ ACTION erctoken::transferfrom( name spender, name from, name to, asset quantity, auto payer = has_auth( to ) ? to : spender; - accounts from_acnts( get_self(), from.value ); - const auto& facnt = from_acnts.get( quantity.symbol.code().raw(), "no balance object found" ); - check( facnt.balance.amount >= quantity.amount, "overdrawn balance" ); - - from_acnts.modify( facnt, spender, [&]( auto& a ) { - a.balance -= quantity; - }); - // sub_balance( from, quantity ); + sub_balance( from, quantity ); add_balance( to, quantity, payer ); allws.modify( it, same_payer, [&]( auto& a ){ @@ -182,7 +170,7 @@ ACTION erctoken::transferfrom( name spender, name from, name to, asset quantity, }); } -ACTION erctoken::incallowance( name owner, name spender, asset quantity ) { +ACTION erc20::incallowance( name owner, name spender, asset quantity ) { require_auth( owner ); check( quantity.is_valid(), "invalid quantity" ); @@ -196,7 +184,7 @@ ACTION erctoken::incallowance( name owner, name spender, asset quantity ) { }); } -ACTION erctoken::decallowance( name owner, name spender, asset quantity ) { +ACTION erc20::decallowance( name owner, name spender, asset quantity ) { require_auth( owner ); check( quantity.is_valid(), "invalid quantity" ); @@ -212,7 +200,7 @@ ACTION erctoken::decallowance( name owner, name spender, asset quantity ) { }); } -ACTION erctoken::open( name owner, const symbol& symbol, name ram_payer ) { +ACTION erc20::open( name owner, const symbol& symbol, name ram_payer ) { require_auth( ram_payer ); auto sym_code_raw = symbol.code().raw(); @@ -228,7 +216,7 @@ ACTION erctoken::open( name owner, const symbol& symbol, name ram_payer ) { } } -ACTION erctoken::close( name owner, const symbol& symbol ) { +ACTION erc20::close( name owner, const symbol& symbol ) { require_auth( owner ); accounts acnts( get_self(), owner.value ); auto it = acnts.find( symbol.code().raw() ); @@ -239,4 +227,4 @@ ACTION erctoken::close( name owner, const symbol& symbol ) { } // namespace eosio -EOSIO_DISPATCH( eosio::erctoken, (issue)(burn)(burnfrom)(transfer)(approve)(transferfrom)(incallowance)(decallowance)(open)(close) ) +EOSIO_DISPATCH( eosio::erc20, (issue)(burn)(burnfrom)(transfer)(approve)(transferfrom)(incallowance)(decallowance)(open)(close) ) diff --git a/eos/erctoken/ecrio.erctoken.hpp b/eos/erc20/erc20.hpp similarity index 88% rename from eos/erctoken/ecrio.erctoken.hpp rename to eos/erc20/erc20.hpp index 86594d2..2fa253b 100644 --- a/eos/erctoken/ecrio.erctoken.hpp +++ b/eos/erc20/erc20.hpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#pragma once - #include #include #include @@ -27,20 +25,20 @@ using namespace eosio; namespace eosio{ using std::string; - CONTRACT erctoken : public contract { + CONTRACT erc20 : public contract { public: - erctoken( name self, name first_receiver, datastream ds ) + erc20( name self, name first_receiver, datastream ds ) : contract( self, first_receiver, ds ), _stat( self, self.value ) { _stat_state = _stat.exists() ? _stat.get() : currency_stat{}; } - ~erctoken() { + ~erc20() { _stat.set( _stat_state, get_self() ); } ACTION issue( name to, asset quantity, string memo ); - ACTION burn( name account, asset quantity, string memo ); + ACTION burn( name owner, asset quantity, string memo ); ACTION burnfrom( name burner, name owner, asset quantity, string memo ); @@ -70,16 +68,16 @@ namespace eosio{ return ac.balance; } - using issue_action = eosio::action_wrapper<"issue"_n, &erctoken::issue>; - using burn_action = eosio::action_wrapper<"burn"_n, &erctoken::burn>; - using burnfrom_action = eosio::action_wrapper<"burnfrom"_n, &erctoken::burnfrom>; - using transfer_action = eosio::action_wrapper<"transfer"_n, &erctoken::transfer>; - using approve_action = eosio::action_wrapper<"approve"_n, &erctoken::approve>; - using transferfrom_action = eosio::action_wrapper<"transferfrom"_n, &erctoken::transferfrom>; - using incallowance_action = eosio::action_wrapper<"incallowance"_n, &erctoken::incallowance>; - using decallowance_action = eosio::action_wrapper<"decallowance"_n, &erctoken::decallowance>; - using open_action = eosio::action_wrapper<"open"_n, &erctoken::open>; - using close_action = eosio::action_wrapper<"close"_n, &erctoken::close>; + using issue_action = eosio::action_wrapper<"issue"_n, &erc20::issue>; + using burn_action = eosio::action_wrapper<"burn"_n, &erc20::burn>; + using burnfrom_action = eosio::action_wrapper<"burnfrom"_n, &erc20::burnfrom>; + using transfer_action = eosio::action_wrapper<"transfer"_n, &erc20::transfer>; + using approve_action = eosio::action_wrapper<"approve"_n, &erc20::approve>; + using transferfrom_action = eosio::action_wrapper<"transferfrom"_n, &erc20::transferfrom>; + using incallowance_action = eosio::action_wrapper<"incallowance"_n, &erc20::incallowance>; + using decallowance_action = eosio::action_wrapper<"decallowance"_n, &erc20::decallowance>; + using open_action = eosio::action_wrapper<"open"_n, &erc20::open>; + using close_action = eosio::action_wrapper<"close"_n, &erc20::close>; private: TABLE account { diff --git a/eos/erc721/erc721.cpp b/eos/erc721/erc721.cpp new file mode 100644 index 0000000..1c5202a --- /dev/null +++ b/eos/erc721/erc721.cpp @@ -0,0 +1,257 @@ +/* + * Copyright 2019 DE-labtory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "erc721.hpp" +using namespace eosio; + +ACTION erc721::create( name issuer, string sym ) { + require_auth( _self ); + + // Check if issuer account exists + check( is_account( issuer ), "issuer account does not exist"); + + // Valid symbol + asset supply(0, symbol( symbol_code( sym.c_str() ), 0) ); + + + auto symbol = supply.symbol; + check( symbol.is_valid(), "invalid symbol name" ); + + // Check if currency with symbol already exists + stats statstable( _self, symbol.code().raw() ); + auto existing = statstable.find( symbol.code().raw() ); + check( existing == statstable.end(), "symbol already exists" ); + + // Create new currency + statstable.emplace( _self, [&]( auto& s ) { + s.supply = supply; + s.issuer = issuer; + }); +} + +ACTION erc721::issue( name to, asset quantity, vector>> tokenInfos, string memo ) { + check( is_account(to), "to account does not exist" ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( sym.precision() == 0, "quantity must be a whole number" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + stats statstable( _self, sym.code().raw() ); + auto existing_st = statstable.find( sym.code().raw() ); + check( existing_st != statstable.end(), "token with symbol does not exist, create token before issue" ); + const auto& st = *existing_st; + + require_auth( st.issuer ); + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity of NFT" ); + check( sym == st.supply.symbol, "symbol precision mismatch" ); + check( quantity.amount == tokenInfos.size(), "mismatch between issue amount and token info" ); + + statstable.modify( st, same_payer, [&]( auto& s ) { + s.supply += quantity; + }); + + tokens tokenstable( _self, sym.code().raw() ); + + auto payer = has_auth( to ) ? to : st.issuer; + + for( auto const& tk : tokenInfos ) { + auto existing_tk = tokenstable.find( tk.first ); + check( existing_tk == tokenstable.end(), "token with symbol already exists" ); + tokenstable.emplace( payer, [&]( auto& token ) { + token.tokenId = tk.first; + token.sym = sym; + token.owner = to; + token.value = tk.second.second; + token.tokenName = tk.second.first; + }); + } + add_balance( to, quantity, payer ); +} + +ACTION erc721::burn( name owner, asset quantity, vector tokenIds, string memo ) { + require_auth( owner ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( sym.precision() == 0, "quantity must be a whole number" ); + + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must burn positive quantity" ); + check( quantity.amount == tokenIds.size(), "mismatch between burn amount and token info" ); + + stats statstable( _self, sym.code().raw() ); + auto existing_st = statstable.find( sym.code().raw() ); + check( existing_st != statstable.end(), "symbol does not exist at stats" ); + const auto& st = *existing_st; + + tokens tokenstable( _self, sym.code().raw() ); + for( auto const& tokenId : tokenIds ) { + auto it = tokenstable.find( tokenId ); + check( it != tokenstable.end(), "token with symbol does not exists" ); + check( it->owner == owner, "not the owner of token" ); + tokenstable.erase( it ); + } + + sub_balance( owner, quantity ); + statstable.modify( st, same_payer, [&]( auto& s ) { + s.supply -= quantity; + }); +} + +ACTION erc721::burnfrom( name burner, string sym, id_type tokenId, string memo ) { + require_auth( burner ); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + check( symbol.is_valid(), "invalid symbol name" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + stats statstable( _self, symbol.code().raw() ); + auto existing_st = statstable.find( symbol.code().raw() ); + check( existing_st != statstable.end(), "symbol does not exist at stats" ); + const auto& st = *existing_st; + + tokens tokenstable( _self, symbol.code().raw() ); + auto it = tokenstable.find( tokenId ); + check( it != tokenstable.end(), "token with symbol does not exists" ); + + require_recipient( it->owner ); + + check( burner == it->spender, "burner is not token spender" ); + + asset unit( 1, symbol ); + + sub_balance( it->owner, unit ); + + tokenstable.erase( it ); + statstable.modify( st, same_payer, [&]( auto& s ) { + s.supply -= unit; + }); +} + +ACTION erc721::transfer( name from, name to, string sym, id_type tokenId, string memo ) { + check( from != to, "cannot transfer to self" ); + require_auth( from ); + check( is_account( to ), "to account does not exist" ); + + require_recipient( from ); + require_recipient( to ); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + + check( symbol.is_valid(), "invalid symbol name" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + tokens tokenstable( _self, symbol.code().raw() ); + auto it = tokenstable.find( tokenId ); + check( it != tokenstable.end(), "token with symbol does not exists" ); + check( from == it->owner, "not the owner of token" ); + + auto payer = has_auth( to ) ? to : from; + + tokenstable.modify( it, payer, [&]( auto& token ) { + token.owner = to; + token.spender = to; + }); + + asset unit( 1, symbol ); + + sub_balance( from, unit ); + add_balance( to, unit, payer ); +} + +ACTION erc721::approve( name owner, name spender, string sym, id_type tokenId ) { + require_auth( owner ); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + + check( symbol.is_valid(), "invalid symbol name" ); + + tokens tokenstable( _self, symbol.code().raw() ); + auto it = tokenstable.find( tokenId ); + check( it != tokenstable.end(), "token with symbol does not exists" ); + check( owner == it->owner, "not the owner of token" ); + + tokenstable.modify( it, same_payer, [&]( auto& token ) { + token.spender = spender; + }); +} + +ACTION erc721::transferfrom( name spender, name to, string sym, id_type tokenId, string memo ) { + require_auth( spender ); + + check( is_account( to ), "to account does not exist"); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + + check( symbol.is_valid(), "invalid symbol name" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + tokens tokenstable( _self, symbol.code().raw() ); + auto it = tokenstable.find( tokenId ); + check( it != tokenstable.end(), "token with symbol does not exists" ); + check( spender == it->spender, "spender is not token spender" ); + name owner = it->owner; + + require_recipient( owner ); + require_recipient( to ); + + auto payer = has_auth( to ) ? to : spender; + + tokenstable.modify( it, payer, [&]( auto& token ) { + token.owner = to; + token.spender = to; + }); + + asset unit( 1, symbol ); + + sub_balance( owner, unit ); + add_balance( to, unit, payer ); +} + +void erc721::sub_balance( name owner, asset value ) { + accounts from_acnts( get_self(), owner.value ); + + const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" ); + check( from.balance.amount >= value.amount, "overdrawn balance" ); + + name payer = !has_auth(owner) ? same_payer : owner; + + from_acnts.modify( from, payer, [&]( auto& a ) { + a.balance -= value; + }); +} + +void erc721::add_balance( name owner, asset value, name ram_payer ) { + accounts to_acnts( get_self(), owner.value ); + auto to = to_acnts.find( value.symbol.code().raw() ); + if( to == to_acnts.end() ) { + to_acnts.emplace( ram_payer, [&]( auto& a ){ + a.balance = value; + }); + } else { + to_acnts.modify( to, same_payer, [&]( auto& a ) { + a.balance += value; + }); + } +} \ No newline at end of file diff --git a/eos/erc721/erc721.hpp b/eos/erc721/erc721.hpp new file mode 100644 index 0000000..496df84 --- /dev/null +++ b/eos/erc721/erc721.hpp @@ -0,0 +1,97 @@ +/* + * Copyright 2019 DE-labtory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +using namespace eosio; +using std::string; +using std::vector; +typedef uint64_t id_type; + +CONTRACT erc721 : public contract { + public: + erc721( name self, name first_receiver, datastream ds ) + : contract( self, first_receiver, ds ) {} + + ACTION create( name issuer, string sym ); + + ACTION issue( name to, asset quantity, vector>> tokenInfos, string memo ); + + ACTION burn( name owner, asset quantity, vector tokenIds, string memo ); + + ACTION burnfrom( name burner, string sym, id_type tokenId, string memo ); + + ACTION transfer( name from, name to, string sym, id_type tokenId, string memo ); + + ACTION approve( name owner, name spender, string sym, id_type tokenId ); + + ACTION transferfrom( name spender, name to, string sym, id_type tokenId, string memo ); + + static asset get_supply( name token_contract_account, symbol_code sym_code ) { + stats statstable( token_contract_account, sym_code.raw() ); + const auto& st = statstable.get( sym_code.raw() ); + return st.supply; + } + + static asset get_balance( name token_contract_account, name owner, symbol_code sym_code ) { + accounts accountstable( token_contract_account, owner.value ); + const auto& ac = accountstable.get( sym_code.raw() ); + return ac.balance; + } + + private: + TABLE account { + asset balance; + + uint64_t primary_key() const { return balance.symbol.code().raw(); } + }; + + TABLE currency_stat { + asset supply; + name issuer; + + uint64_t primary_key() const { return supply.symbol.code().raw(); } + uint64_t get_issuer() const { return issuer.value; } + }; + + TABLE token { + id_type tokenId; // Unique 64 bit identifier, + symbol sym; // token symbol + name owner; // token owner + asset value; // token value + name tokenName; // token name + name spender; // token spender + + id_type primary_key() const { return tokenId; } + uint64_t get_owner() const { return owner.value; } + }; + + typedef eosio::multi_index<"accounts"_n, account> accounts; + + typedef eosio::multi_index<"stat"_n, currency_stat, + indexed_by< "byissuer"_n, const_mem_fun< currency_stat, uint64_t, ¤cy_stat::get_issuer> > + > stats; + + typedef eosio::multi_index<"token"_n, token, + indexed_by< "byowner"_n, const_mem_fun< token, uint64_t, &token::get_owner> > + > tokens; + + void sub_balance(name owner, asset value); + void add_balance(name owner, asset value, name ram_payer); +}; \ No newline at end of file From 07fceaba9e57b29d3267d35e46114dac09bc9c30 Mon Sep 17 00:00:00 2001 From: emperorhan Date: Fri, 31 May 2019 17:55:08 +0900 Subject: [PATCH 7/7] Add: advanced multiple-function token --- eos/amft/amft.cpp | 612 ++++++++++++++++++++++++++++++++++++++++++ eos/amft/amft.hpp | 161 +++++++++++ eos/erc20/erc20.cpp | 230 ---------------- eos/erc20/erc20.hpp | 111 -------- eos/erc721/erc721.cpp | 257 ------------------ eos/erc721/erc721.hpp | 97 ------- 6 files changed, 773 insertions(+), 695 deletions(-) create mode 100644 eos/amft/amft.cpp create mode 100644 eos/amft/amft.hpp delete mode 100644 eos/erc20/erc20.cpp delete mode 100644 eos/erc20/erc20.hpp delete mode 100644 eos/erc721/erc721.cpp delete mode 100644 eos/erc721/erc721.hpp diff --git a/eos/amft/amft.cpp b/eos/amft/amft.cpp new file mode 100644 index 0000000..23ae8b6 --- /dev/null +++ b/eos/amft/amft.cpp @@ -0,0 +1,612 @@ +#include "amft.hpp" +using namespace eosio; + +ACTION amft::issue( name to, asset quantity, string memo ) { + require_auth( get_self() ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + + _stat_state.supply += quantity; + + add_balance( get_self(), quantity, get_self() ); + + if( to != get_self() ) { + SEND_INLINE_ACTION( *this, transfer, { {get_self(), "active"_n} }, + { get_self(), to, quantity, memo } + ); + } +} + +ACTION amft::burn( name owner, asset quantity, string memo ) { + require_auth( owner ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must burn positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + sub_balance( owner, quantity ); + _stat_state.supply -= quantity; + + allowances allws( get_self(), owner.value ); + auto existing_allw = allws.find( sym.code().raw() ); + if( existing_allw != allws.end() ) { + accounts acnts( get_self(), owner.value ); + auto existing_ac = acnts.find( sym.code().raw() ); + if( !existing_ac->balance.amount ) allws.erase(existing_allw); + else if( existing_allw->balance.amount > existing_ac->balance.amount ){ + allws.modify( existing_allw, same_payer, [&]( auto& a ) { + a.balance.amount = existing_ac->balance.amount; + }); + } + } +} + +ACTION amft::burnfrom( name burner, name owner, asset quantity, string memo ) { + require_auth( burner ); + check( burner != owner, "cannot burnfrom self" ); + check( is_account( owner ), "owner account does not exist"); + + require_recipient( owner ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must be positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + allowances allws( get_self(), owner.value ); + auto existing_allw = allws.require_find( sym.code().raw(), "no symbol in the allowance table" ); + check( existing_allw->spender == burner, "you are not a spender" ); + check( existing_allw->balance.amount >= quantity.amount, "burner does not have enough allowed amount" ); + + if( existing_allw->balance.amount == quantity.amount ) allws.erase( existing_allw ); + else { + allws.modify( existing_allw, same_payer, [&]( auto& a ) { + a.balance -= quantity; + }); + } + + sub_balance( owner, quantity ); + + _stat_state.supply -= quantity; +} + +ACTION amft::transfer( name from, + name to, + asset quantity, + string memo ) { + check( from != to, "cannot transfer to self" ); + require_auth( from ); + check( is_account( to ), "to account does not exist"); + + require_recipient( from ); + require_recipient( to ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must transfer positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + auto payer = has_auth( to ) ? to : from; + + sub_balance( from, quantity ); + add_balance( to, quantity, payer ); + + allowances allws( get_self(), from.value ); + auto existing_allw = allws.find( sym.code().raw() ); + if( existing_allw != allws.end() ) { + accounts acnts( get_self(), from.value ); + auto existing_ac = acnts.find( sym.code().raw() ); + if( !existing_ac->balance.amount ) allws.erase(existing_allw); + else if( existing_allw->balance.amount > existing_ac->balance.amount ){ + allws.modify( existing_allw, same_payer, [&]( auto& a ) { + a.balance.amount = existing_ac->balance.amount; + }); + } + } +} + +ACTION amft::approve( name owner, name spender, asset quantity ) { + require_auth( owner ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + + accounts acnts( get_self(), owner.value ); + auto existing_ac = acnts.require_find( sym.code().raw(), "owner does not have token with symbol" ); + + check( existing_ac->balance.amount >= quantity.amount, "not enough balance" ); + + allowances allws( get_self(), owner.value ); + auto existing_allw = allws.find( sym.code().raw() ); + if( existing_allw == allws.end() ) { + allws.emplace( owner, [&]( auto& a ){ + a.balance = quantity; + a.spender = spender; + }); + } else { + allws.modify( existing_allw, same_payer, [&]( auto& a ){ + a.balance = quantity; + a.spender = spender; + }); + } +} + +ACTION amft::transferfrom( name spender, name from, name to, asset quantity, string memo ) { + require_auth( spender ); + check( from != to, "cannot transfer to self" ); + check( is_account( from ), "from account does not exist"); + check( is_account( to ), "to account does not exist"); + + require_recipient( from ); + require_recipient( to ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must transfer positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + allowances allws( get_self(), from.value ); + auto existing_allw = allws.require_find( sym.code().raw(), "no symbol in the allowance table" ); + check( existing_allw->balance.amount >= quantity.amount, "spender does not have enough allowed amount" ); + + if( existing_allw->balance.amount == quantity.amount ) allws.erase( existing_allw ); + else { + allws.modify( existing_allw, same_payer, [&]( auto& a ) { + a.balance -= quantity; + }); + } + + auto payer = has_auth( to ) ? to : spender; + + sub_balance( from, quantity ); + add_balance( to, quantity, payer ); +} + +ACTION amft::incallowance( name owner, asset quantity ) { + require_auth( owner ); + + auto sym = quantity.symbol; + + accounts acnts( get_self(), owner.value ); + auto existing_ac = acnts.require_find( sym.code().raw(), "owner does not have token with symbol" ); + + allowances allws( get_self(), owner.value ); + auto existing_allw = allws.require_find( sym.code().raw(), "spender is not registed" ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( existing_ac->balance.amount >= existing_allw->balance.amount + quantity.amount, "owner does not have enough increase allow amount" ); + + allws.modify( existing_allw, same_payer, [&]( auto& a ) { + a.balance += quantity; + }); +} + +ACTION amft::decallowance( name owner, asset quantity ) { + require_auth( owner ); + + auto sym = quantity.symbol; + + allowances allws( get_self(), owner.value ); + auto existing_allw = allws.require_find( sym.code().raw(), "spender is not registed" ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity" ); + check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); + check( existing_allw->balance >= quantity, "there is not enough balance" ); + + allws.modify( existing_allw, same_payer, [&]( auto& a ) { + a.balance -= quantity; + }); +} + +ACTION amft::open( name owner, const symbol& symbol, name ram_payer ) { + require_auth( ram_payer ); + + auto sym_code_raw = symbol.code().raw(); + + check( _stat_state.supply.symbol == symbol, "symbol precision mismatch" ); + + accounts acnts( get_self(), owner.value ); + auto existing_ac = acnts.find( sym_code_raw ); + if( existing_ac == acnts.end() ) { + acnts.emplace( ram_payer, [&]( auto& a ){ + a.balance = asset{0, symbol}; + }); + } +} + +ACTION amft::close( name owner, const symbol& symbol ) { + require_auth( owner ); + accounts acnts( get_self(), owner.value ); + auto existing_ac = acnts.find( symbol.code().raw() ); + check( existing_ac != acnts.end(), "Balance row already deleted or never existed. Action won't have any effect." ); + check( existing_ac->balance.amount == 0, "Cannot close because the balance is not zero." ); + acnts.erase( existing_ac ); +} + +ACTION amft::createnft( name issuer, string sym ) { + require_auth( _self ); + + // Check if issuer account exists + check( is_account( issuer ), "issuer account does not exist"); + + // Valid symbol + asset supply(0, symbol( symbol_code( sym.c_str() ), 0) ); + + auto symbol = supply.symbol; + check( symbol.is_valid(), "invalid symbol name" ); + + // Check if currency with symbol already exists + stats statstable( _self, symbol.code().raw() ); + auto existing = statstable.find( symbol.code().raw() ); + check( existing == statstable.end(), "symbol already exists" ); + + // Create new currency + statstable.emplace( _self, [&]( auto& s ) { + s.supply = supply; + s.issuer = issuer; + }); +} + +ACTION amft::issuenft( name to, asset quantity, vector>> token_infos, string memo ) { + check( is_account(to), "to account does not exist" ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( sym.precision() == 0, "quantity must be a whole number" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + stats statstable( _self, sym.code().raw() ); + auto existing_st = statstable.find( sym.code().raw() ); + check( existing_st != statstable.end(), "token with symbol does not exist, create token before issue" ); + const auto& st = *existing_st; + + require_auth( st.issuer ); + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must issue positive quantity of NFT" ); + check( sym == st.supply.symbol, "symbol precision mismatch" ); + check( quantity.amount == token_infos.size(), "mismatch between issue amount and token info" ); + + statstable.modify( st, same_payer, [&]( auto& s ) { + s.supply += quantity; + }); + + tokens tokenstable( _self, sym.code().raw() ); + + auto payer = has_auth( to ) ? to : st.issuer; + + for( auto const& tk : token_infos ) { + auto existing_tk = tokenstable.find( tk.first ); + check( existing_tk == tokenstable.end(), "token with symbol already exists" ); + tokenstable.emplace( payer, [&]( auto& token ) { + token.token_id = tk.first; + token.owner = to; + token.value = tk.second.second; + token.tokenName = tk.second.first; + }); + } + add_balance( to, quantity, payer ); +} + +ACTION amft::burnnft( name owner, asset quantity, vector token_ids, string memo ) { + require_auth( owner ); + + auto sym = quantity.symbol; + check( sym.is_valid(), "invalid symbol name" ); + check( sym.precision() == 0, "quantity must be a whole number" ); + + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + check( quantity.is_valid(), "invalid quantity" ); + check( quantity.amount > 0, "must burn positive quantity" ); + check( quantity.amount == token_ids.size(), "mismatch between burn amount and token info" ); + + stats statstable( _self, sym.code().raw() ); + auto existing_st = statstable.find( sym.code().raw() ); + check( existing_st != statstable.end(), "symbol does not exist at stats" ); + const auto& st = *existing_st; + + tokens tokenstable( _self, sym.code().raw() ); + for( auto const& token_id : token_ids ) { + auto existing_tk = tokenstable.find( token_id ); + check( existing_tk != tokenstable.end(), "token with symbol does not exists" ); + check( existing_tk->owner == owner, "not the owner of token" ); + tokenstable.erase( existing_tk ); + } + + sub_balance( owner, quantity ); + statstable.modify( st, same_payer, [&]( auto& s ) { + s.supply -= quantity; + }); +} + +ACTION amft::burnnftfrom( name burner, string sym, id_type token_id, string memo ) { + require_auth( burner ); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + check( symbol.is_valid(), "invalid symbol name" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + stats statstable( _self, symbol.code().raw() ); + auto existing_st = statstable.find( symbol.code().raw() ); + check( existing_st != statstable.end(), "symbol does not exist at stats" ); + const auto& st = *existing_st; + + tokens tokenstable( _self, symbol.code().raw() ); + auto existing_tk = tokenstable.find( token_id ); + check( existing_tk != tokenstable.end(), "token with symbol does not exists" ); + + require_recipient( existing_tk->owner ); + + check( burner == existing_tk->spender, "burner is not token spender" ); + + asset unit( 1, symbol ); + + sub_balance( existing_tk->owner, unit ); + + tokenstable.erase( existing_tk ); + statstable.modify( st, same_payer, [&]( auto& s ) { + s.supply -= unit; + }); +} + +ACTION amft::send( name from, name to, string sym, id_type token_id, string memo ) { + check( from != to, "cannot transfer to self" ); + require_auth( from ); + check( is_account( to ), "to account does not exist" ); + + require_recipient( from ); + require_recipient( to ); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + + check( symbol.is_valid(), "invalid symbol name" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + tokens tokenstable( _self, symbol.code().raw() ); + auto existing_tk = tokenstable.find( token_id ); + check( existing_tk != tokenstable.end(), "token with symbol does not exists" ); + check( from == existing_tk->owner, "not the owner of token" ); + check( existing_tk->spender != _self, "if spender is _self, it can not transfer" ); + + auto payer = has_auth( to ) ? to : from; + + tokenstable.modify( existing_tk, payer, [&]( auto& token ) { + token.owner = to; + token.spender = to; + }); + + asset unit( 1, symbol ); + + sub_balance( from, unit ); + add_balance( to, unit, payer ); +} + +ACTION amft::approvenft( name owner, name spender, string sym, id_type token_id ) { + require_auth( owner ); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + + check( symbol.is_valid(), "invalid symbol name" ); + + tokens tokenstable( _self, symbol.code().raw() ); + auto existing_tk = tokenstable.find( token_id ); + check( existing_tk != tokenstable.end(), "token with symbol does not exists" ); + check( owner == existing_tk->owner, "not the owner of token" ); + check( owner == _self || existing_tk->spender != _self, "if spender is _self, it can not be changed" ); + + tokenstable.modify( existing_tk, same_payer, [&]( auto& token ) { + token.spender = spender; + }); +} + +ACTION amft::sendfrom( name spender, name to, string sym, id_type token_id, string memo ) { + require_auth( spender ); + + check( is_account( to ), "to account does not exist"); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + + check( symbol.is_valid(), "invalid symbol name" ); + check( memo.size() <= 256, "memo has more than 256 bytes" ); + + tokens tokenstable( _self, symbol.code().raw() ); + auto existing_tk = tokenstable.find( token_id ); + check( existing_tk != tokenstable.end(), "token with symbol does not exists" ); + check( spender == existing_tk->spender, "spender is not token spender" ); + name owner = existing_tk->owner; + + require_recipient( owner ); + require_recipient( to ); + + auto payer = has_auth( to ) ? to : spender; + + tokenstable.modify( existing_tk, payer, [&]( auto& token ) { + token.owner = to; + token.spender = to; + }); + + asset unit( 1, symbol ); + + sub_balance( owner, unit ); + add_balance( to, unit, payer ); +} + +void amft::sub_balance( name owner, asset value ) { + accounts from_acnts( _self, owner.value ); + + const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" ); + check( from.balance.amount >= value.amount, "overdrawn balance" ); + + name payer = !has_auth(owner) ? same_payer : owner; + + from_acnts.modify( from, payer, [&]( auto& a ) { + a.balance -= value; + }); +} + +void amft::add_balance( name owner, asset value, name ram_payer ) { + accounts to_acnts( _self, owner.value ); + auto to = to_acnts.find( value.symbol.code().raw() ); + if( to == to_acnts.end() ) { + to_acnts.emplace( ram_payer, [&]( auto& a ){ + a.balance = value; + }); + } else { + to_acnts.modify( to, same_payer, [&]( auto& a ) { + a.balance += value; + }); + } +} + +ACTION amft::auctiontoken( name auctioneer, string sym, id_type token_id, asset min_price, uint32_t sec ) { + require_auth( auctioneer ); + + require_recipient( auctioneer ); + require_recipient( _self ); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + check( symbol.is_valid(), "invalid symbol name" ); + + check( sec > 0, "sec must be a positive integer" ); + + const time_point_sec deadline = time_point_sec(now()) + sec; + + tokens tokenstable( _self, symbol.code().raw() ); + auto existing_tk = tokenstable.find( token_id ); + check( existing_tk != tokenstable.end(), "token with symbol does not exists" ); + check( existing_tk->owner == auctioneer, "not the owner of token" ); + + token_bids tokenbidstable( _self, symbol.code().raw() ); + auto existing_bid = tokenbidstable.find( token_id ); + check( existing_bid == tokenbidstable.end(), "token bid already exist" ); + + check( min_price.amount > 0, "token bid already exist" ); + + tokenbidstable.emplace( auctioneer, [&]( auto& b ){ + b.token_id = token_id; + b.high_bidder = auctioneer; + b.high_bid = min_price.amount; + b.deadline = deadline; + }); + + tokenstable.modify( existing_tk, same_payer, [&]( auto& token ) { + token.spender = _self; + }); +} + +ACTION amft::bidtoken( name bidder, string sym, id_type token_id, asset bid ) { + require_auth( bidder ); + + check( bid.symbol == key_symbol, "bid asset must be key currency symbol" ); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + check( symbol.is_valid(), "invalid symbol name" ); + + tokens tokenstable( _self, symbol.code().raw() ); + auto existing_tk = tokenstable.find( token_id ); + check( existing_tk != tokenstable.end(), "token with symbol does not exists" ); + + check( bidder != existing_tk->owner, "token owners can not bid" ); + + token_bids tokenbidstable( _self, symbol.code().raw() ); + auto existing_bid = tokenbidstable.find( token_id ); + check( existing_bid != tokenbidstable.end(), "token auction is not exist" ); + + const time_point_sec time_now = time_point_sec(now()); + check( existing_bid->deadline > time_now, "the auction deadline has passed" ); + check( bid.amount > existing_bid->high_bid, "the bid amount is insufficient" ); + + if( existing_bid->high_bidder != existing_tk->owner ) { + asset refund_kc(existing_bid->high_bid, key_symbol); + // refund + action( + permission_level{ _self, "active"_n }, + _self, "transfer"_n, + std::make_tuple( _self, existing_bid->high_bidder, refund_kc, std::string("refund bidding fee")) + ).send(); + } + + // new high bidder + tokenbidstable.modify( existing_bid, same_payer, [&]( auto& b ){ + b.high_bidder = bidder; + b.high_bid = bid.amount; + }); + + sub_balance( bidder, bid ); + add_balance( _self, bid, _self ); + + bidresult_action bid_act( bidder, std::vector{ } ); + bid_act.send( bid ); +} + +ACTION amft::claimtoken( name requester, string sym, id_type token_id ) { + require_auth(requester); + + asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); + auto symbol = as.symbol; + check( symbol.is_valid(), "invalid symbol name" ); + + tokens tokenstable( _self, symbol.code().raw() ); + auto existing_tk = tokenstable.find( token_id ); + check( existing_tk != tokenstable.end(), "token with symbol does not exists" ); + + token_bids tokenbidstable( _self, symbol.code().raw() ); + auto existing_bid = tokenbidstable.find( token_id ); + check( existing_bid != tokenbidstable.end(), "token auction is not exist" ); + + const time_point_sec time_now = time_point_sec(now()); + check( existing_bid->deadline <= time_now, "deadline not over" ); + check( requester == existing_tk->owner || requester == existing_bid->high_bidder, "the requester is not authorized" ); + + if( existing_bid->high_bidder != existing_tk->owner ) { + asset payment_kc(existing_bid->high_bid, key_symbol); + + // bidding fee payment + action( + permission_level{ _self, "active"_n }, + _self, "transfer"_n, + std::make_tuple( _self, existing_tk->owner, payment_kc, std::string("receive auction sale money")) + ).send(); + + // nft ownership change + action( + permission_level{ _self, "active"_n }, + _self, "sendfrom"_n, + std::make_tuple( _self, existing_bid->high_bidder, sym, token_id, std::string("receive bid tokens")) + ).send(); + } else { + tokenstable.modify( existing_tk, same_payer, [&]( auto& token ) { + token.spender = existing_tk->owner; + }); + } + + tokenbidstable.erase( existing_bid ); +} \ No newline at end of file diff --git a/eos/amft/amft.hpp b/eos/amft/amft.hpp new file mode 100644 index 0000000..ec9b6e6 --- /dev/null +++ b/eos/amft/amft.hpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include + +using namespace eosio; +using std::string; +using std::vector; +typedef uint64_t id_type; + +CONTRACT amft : public contract { + public: + static constexpr symbol key_symbol = symbol(symbol_code("KRW"), 3); + amft( name self, name first_receiver, datastream ds ) + : contract( self, first_receiver, ds ), _stat( self, self.value ) { + _stat_state = _stat.exists() ? _stat.get() : key_currency_stat{}; + } + ~amft() { + _stat.set( _stat_state, get_self() ); + } + + ACTION issue( name to, asset quantity, string memo ); + + ACTION burn( name owner, asset quantity, string memo ); + + ACTION burnfrom( name burner, name owner, asset quantity, string memo ); + + ACTION transfer( name from, name to, asset quantity, string memo ); + + ACTION approve( name owner, name spender, asset quantity ); + + ACTION transferfrom( name spender, name from, name to, asset quantity, string memo ); + + ACTION incallowance( name owner, asset quantity ); + + ACTION decallowance( name owner, asset quantity ); + + ACTION open( name owner, const symbol& symbol, name ram_payer ); + + ACTION close( name owner, const symbol& symbol ); + + ACTION createnft( name issuer, string sym ); // nft + + ACTION issuenft( name to, asset quantity, vector>> token_infos, string memo ); + + ACTION burnnft( name owner, asset quantity, vector token_ids, string memo ); + + ACTION burnnftfrom( name burner, string sym, id_type token_id, string memo ); + + ACTION send( name from, name to, string sym, id_type token_id, string memo ); + + ACTION approvenft( name owner, name spender, string sym, id_type token_id ); + + ACTION sendfrom( name spender, name to, string sym, id_type token_id, string memo ); + + ACTION auctiontoken( name auctioneer, string sym, id_type token_id, asset min_price, uint32_t sec ); + + ACTION bidtoken( name bidder, string sym, id_type token_id, asset bid ); + + ACTION claimtoken( name requester, string sym, id_type token_id ); + + // dummy action + ACTION bidresult( const asset& bid_key_currency ) { }; + + using bidresult_action = action_wrapper<"bidresult"_n, &amft::bidresult>; + + static asset get_supply( name token_contract_account, symbol_code sym_code ) { + stats statstable( token_contract_account, sym_code.raw() ); + const auto& st = statstable.get( sym_code.raw() ); + return st.supply; + } + + static asset get_balance( name token_contract_account, name owner, symbol_code sym_code ) { + accounts accountstable( token_contract_account, owner.value ); + const auto& ac = accountstable.get( sym_code.raw() ); + return ac.balance; + } + + static asset get_key_supply( name token_contract_account ) { + stat_singleton _stat( token_contract_account, token_contract_account.value ); + const auto& st = _stat.get(); + return st.supply; + } + + static asset get_key_balance( name token_contract_account, name owner, symbol_code sym_code ) { + accounts accountstable( token_contract_account, owner.value ); + const auto& ac = accountstable.get( sym_code.raw() ); + return ac.balance; + } + + private: + struct [[eosio::table("keystat")]] key_currency_stat { + asset supply = asset{0, symbol(symbol_code("KRW"), 3)}; + }; + + TABLE account { + asset balance; + + uint64_t primary_key() const { return balance.symbol.code().raw(); } + }; + + TABLE allowance { + asset balance; + name spender; + + uint64_t primary_key() const { return balance.symbol.code().raw(); } + }; + + TABLE currency_stats { + asset supply; + name issuer; + + uint64_t primary_key() const { return supply.symbol.code().raw(); } + uint64_t get_issuer() const { return issuer.value; } + }; + + TABLE token { + id_type token_id; // Unique 64 bit identifier, + name owner; // token owner + asset value; // token value + name tokenName; // token name + name spender; // token spender + + id_type primary_key() const { return token_id; } + uint64_t get_owner() const { return owner.value; } + }; + + TABLE token_bid { + id_type token_id; + name high_bidder; + int64_t high_bid = 0; + time_point_sec deadline; + + id_type primary_key() const { return token_id; } + }; + + typedef eosio::singleton< "keystat"_n, key_currency_stat > stat_singleton; + + typedef eosio::multi_index<"accounts"_n, account> accounts; + + typedef eosio::multi_index< "allowances"_n, allowance > allowances; + + typedef eosio::multi_index<"stat"_n, currency_stats, + indexed_by< "byissuer"_n, const_mem_fun< currency_stats, uint64_t, ¤cy_stats::get_issuer> > + > stats; + + typedef eosio::multi_index<"token"_n, token, + indexed_by< "byowner"_n, const_mem_fun< token, uint64_t, &token::get_owner> > + > tokens; + + typedef eosio::multi_index< "tokenbids"_n, token_bid> token_bids; + + stat_singleton _stat; + + key_currency_stat _stat_state; + + void sub_balance(name owner, asset value); + void add_balance(name owner, asset value, name ram_payer); +}; \ No newline at end of file diff --git a/eos/erc20/erc20.cpp b/eos/erc20/erc20.cpp deleted file mode 100644 index cfe4854..0000000 --- a/eos/erc20/erc20.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2019 DE-labtory - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "erc20.hpp" - -namespace eosio { -ACTION erc20::issue( name to, asset quantity, string memo ) { - require_auth( get_self() ); - - auto sym = quantity.symbol; - check( sym.is_valid(), "invalid symbol name" ); - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must issue positive quantity" ); - - check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); - - _stat_state.supply += quantity; - - add_balance( to, quantity, get_self() ); - - if( to != get_self() ) { - SEND_INLINE_ACTION( *this, transfer, { {get_self(), "active"_n} }, - { get_self(), to, quantity, memo } - ); - } -} - -ACTION erc20::burn( name owner, asset quantity, string memo ) { - require_auth( owner ); - - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must burn positive quantity" ); - check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - sub_balance( owner, quantity ); - _stat_state.supply -= quantity; -} - -ACTION erc20::burnfrom( name burner, name owner, asset quantity, string memo ) { - require_auth( burner ); - check( burner != owner, "cannot burnfrom self" ); - check( is_account( owner ), "owner account does not exist"); - - require_recipient( owner ); - - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must be positive quantity" ); - check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - allowances allws( get_self(), owner.value ); - auto itr = allws.require_find( burner.value, "burner is not exist" ); - check( itr->balance.amount >= quantity.amount, "burner does not have enough allowed amount" ); - allws.modify( itr, same_payer, [&]( auto& a ) { - a.balance -= quantity; - }); - - sub_balance( owner, quantity ); - - _stat_state.supply -= quantity; -} - -ACTION erc20::transfer( name from, - name to, - asset quantity, - string memo ) { - check( from != to, "cannot transfer to self" ); - require_auth( from ); - check( is_account( to ), "to account does not exist"); - - require_recipient( from ); - require_recipient( to ); - - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must transfer positive quantity" ); - check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - auto payer = has_auth( to ) ? to : from; - - sub_balance( from, quantity ); - add_balance( to, quantity, payer ); -} - -void erc20::sub_balance( name owner, asset value ) { - accounts from_acnts( get_self(), owner.value ); - - const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" ); - check( from.balance.amount >= value.amount, "overdrawn balance" ); - - name payer = !has_auth(owner) ? same_payer : owner; - - from_acnts.modify( from, payer, [&]( auto& a ) { - a.balance -= value; - }); -} - -void erc20::add_balance( name owner, asset value, name ram_payer ) { - accounts to_acnts( get_self(), owner.value ); - auto to = to_acnts.find( value.symbol.code().raw() ); - if( to == to_acnts.end() ) { - to_acnts.emplace( ram_payer, [&]( auto& a ){ - a.balance = value; - }); - } else { - to_acnts.modify( to, same_payer, [&]( auto& a ) { - a.balance += value; - }); - } -} - -ACTION erc20::approve( name owner, name spender, asset quantity ) { - require_auth( owner ); - - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must issue positive quantity" ); - check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); - - allowances allws( get_self(), owner.value ); - auto it = allws.find( spender.value ); - check( it == allws.end(), "spender already exist" ); - allws.emplace( owner, [&]( auto& a ){ - a.spender = spender; - a.balance = quantity; - }); -} - -ACTION erc20::transferfrom( name spender, name from, name to, asset quantity, string memo ) { - require_auth( spender ); - check( from != to, "cannot transfer to self" ); - check( is_account( from ), "from account does not exist"); - check( is_account( to ), "to account does not exist"); - - require_recipient( from ); - require_recipient( to ); - - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must transfer positive quantity" ); - check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - allowances allws( get_self(), from.value ); - auto it = allws.require_find( spender.value, "spender is not exist" ); - check( it->balance.symbol == quantity.symbol, "symbol precision mismatch" ); - check( it->balance.amount >= quantity.amount, "spender does not have enough allowed amount" ); - - auto payer = has_auth( to ) ? to : spender; - - sub_balance( from, quantity ); - add_balance( to, quantity, payer ); - - allws.modify( it, same_payer, [&]( auto& a ){ - a.balance -= quantity; - }); -} - -ACTION erc20::incallowance( name owner, name spender, asset quantity ) { - require_auth( owner ); - - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must issue positive quantity" ); - check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); - - allowances allws( get_self(), owner.value ); - auto it = allws.require_find( spender.value, "spender is not registed" ); - allws.modify( it, same_payer, [&]( auto& a ) { - a.balance += quantity; - }); -} - -ACTION erc20::decallowance( name owner, name spender, asset quantity ) { - require_auth( owner ); - - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must issue positive quantity" ); - check( quantity.symbol == _stat_state.supply.symbol, "symbol precision mismatch" ); - - allowances allws( get_self(), owner.value ); - auto it = allws.require_find( spender.value, "spender is not registed" ); - check( it->balance >= quantity, "there is not enough balance" ); - - allws.modify( it, same_payer, [&]( auto& a ) { - a.balance -= quantity; - }); -} - -ACTION erc20::open( name owner, const symbol& symbol, name ram_payer ) { - require_auth( ram_payer ); - - auto sym_code_raw = symbol.code().raw(); - - check( _stat_state.supply.symbol == symbol, "symbol precision mismatch" ); - - accounts acnts( get_self(), owner.value ); - auto it = acnts.find( sym_code_raw ); - if( it == acnts.end() ) { - acnts.emplace( ram_payer, [&]( auto& a ){ - a.balance = asset{0, symbol}; - }); - } -} - -ACTION erc20::close( name owner, const symbol& symbol ) { - require_auth( owner ); - accounts acnts( get_self(), owner.value ); - auto it = acnts.find( symbol.code().raw() ); - check( it != acnts.end(), "Balance row already deleted or never existed. Action won't have any effect." ); - check( it->balance.amount == 0, "Cannot close because the balance is not zero." ); - acnts.erase( it ); -} - -} // namespace eosio - -EOSIO_DISPATCH( eosio::erc20, (issue)(burn)(burnfrom)(transfer)(approve)(transferfrom)(incallowance)(decallowance)(open)(close) ) diff --git a/eos/erc20/erc20.hpp b/eos/erc20/erc20.hpp deleted file mode 100644 index 2fa253b..0000000 --- a/eos/erc20/erc20.hpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2019 DE-labtory - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include - -using namespace eosio; - -namespace eosio{ - using std::string; - - CONTRACT erc20 : public contract { - public: - erc20( name self, name first_receiver, datastream ds ) - : contract( self, first_receiver, ds ), _stat( self, self.value ) { - _stat_state = _stat.exists() ? _stat.get() : currency_stat{}; - } - - ~erc20() { - _stat.set( _stat_state, get_self() ); - } - - ACTION issue( name to, asset quantity, string memo ); - - ACTION burn( name owner, asset quantity, string memo ); - - ACTION burnfrom( name burner, name owner, asset quantity, string memo ); - - ACTION transfer( name from, name to, asset quantity, string memo ); - - ACTION approve( name owner, name spender, asset quantity ); - - ACTION transferfrom( name spender, name from, name to, asset quantity, string memo ); - - ACTION incallowance( name owner, name spender, asset quantity ); - - ACTION decallowance( name owner, name spender, asset quantity ); - - ACTION open( name owner, const symbol& symbol, name ram_payer ); - - ACTION close( name owner, const symbol& symbol ); - - static asset get_supply( name token_contract_account ){ - stat_singleton _stat( token_contract_account, token_contract_account.value ); - const auto& st = _stat.get(); - return st.supply; - } - - static asset get_balance( name token_contract_account, name owner, symbol_code sym_code ){ - accounts accountstable( token_contract_account, owner.value ); - const auto& ac = accountstable.get( sym_code.raw() ); - return ac.balance; - } - - using issue_action = eosio::action_wrapper<"issue"_n, &erc20::issue>; - using burn_action = eosio::action_wrapper<"burn"_n, &erc20::burn>; - using burnfrom_action = eosio::action_wrapper<"burnfrom"_n, &erc20::burnfrom>; - using transfer_action = eosio::action_wrapper<"transfer"_n, &erc20::transfer>; - using approve_action = eosio::action_wrapper<"approve"_n, &erc20::approve>; - using transferfrom_action = eosio::action_wrapper<"transferfrom"_n, &erc20::transferfrom>; - using incallowance_action = eosio::action_wrapper<"incallowance"_n, &erc20::incallowance>; - using decallowance_action = eosio::action_wrapper<"decallowance"_n, &erc20::decallowance>; - using open_action = eosio::action_wrapper<"open"_n, &erc20::open>; - using close_action = eosio::action_wrapper<"close"_n, &erc20::close>; - - private: - TABLE account { - asset balance; - uint64_t primary_key()const { return balance.symbol.code().raw(); } - }; - - struct [[eosio::table("stat")]] currency_stat { - asset supply = asset{0, symbol(symbol_code("TEST"), 5)}; - }; - - TABLE allowance { - name spender; - asset balance; - - uint64_t primary_key()const { return spender.value; } - }; - - typedef eosio::multi_index< "accounts"_n, account > accounts; - typedef eosio::multi_index< "allowances"_n, allowance > allowances; - typedef eosio::singleton< "stat"_n, currency_stat > stat_singleton; - - stat_singleton _stat; - - currency_stat _stat_state; - - void sub_balance( name owner, asset value ); - void add_balance( name owner, asset value, name ram_payer ); - }; - -} // namespace eosio diff --git a/eos/erc721/erc721.cpp b/eos/erc721/erc721.cpp deleted file mode 100644 index 1c5202a..0000000 --- a/eos/erc721/erc721.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2019 DE-labtory - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "erc721.hpp" -using namespace eosio; - -ACTION erc721::create( name issuer, string sym ) { - require_auth( _self ); - - // Check if issuer account exists - check( is_account( issuer ), "issuer account does not exist"); - - // Valid symbol - asset supply(0, symbol( symbol_code( sym.c_str() ), 0) ); - - - auto symbol = supply.symbol; - check( symbol.is_valid(), "invalid symbol name" ); - - // Check if currency with symbol already exists - stats statstable( _self, symbol.code().raw() ); - auto existing = statstable.find( symbol.code().raw() ); - check( existing == statstable.end(), "symbol already exists" ); - - // Create new currency - statstable.emplace( _self, [&]( auto& s ) { - s.supply = supply; - s.issuer = issuer; - }); -} - -ACTION erc721::issue( name to, asset quantity, vector>> tokenInfos, string memo ) { - check( is_account(to), "to account does not exist" ); - - auto sym = quantity.symbol; - check( sym.is_valid(), "invalid symbol name" ); - check( sym.precision() == 0, "quantity must be a whole number" ); - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - stats statstable( _self, sym.code().raw() ); - auto existing_st = statstable.find( sym.code().raw() ); - check( existing_st != statstable.end(), "token with symbol does not exist, create token before issue" ); - const auto& st = *existing_st; - - require_auth( st.issuer ); - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must issue positive quantity of NFT" ); - check( sym == st.supply.symbol, "symbol precision mismatch" ); - check( quantity.amount == tokenInfos.size(), "mismatch between issue amount and token info" ); - - statstable.modify( st, same_payer, [&]( auto& s ) { - s.supply += quantity; - }); - - tokens tokenstable( _self, sym.code().raw() ); - - auto payer = has_auth( to ) ? to : st.issuer; - - for( auto const& tk : tokenInfos ) { - auto existing_tk = tokenstable.find( tk.first ); - check( existing_tk == tokenstable.end(), "token with symbol already exists" ); - tokenstable.emplace( payer, [&]( auto& token ) { - token.tokenId = tk.first; - token.sym = sym; - token.owner = to; - token.value = tk.second.second; - token.tokenName = tk.second.first; - }); - } - add_balance( to, quantity, payer ); -} - -ACTION erc721::burn( name owner, asset quantity, vector tokenIds, string memo ) { - require_auth( owner ); - - auto sym = quantity.symbol; - check( sym.is_valid(), "invalid symbol name" ); - check( sym.precision() == 0, "quantity must be a whole number" ); - - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - check( quantity.is_valid(), "invalid quantity" ); - check( quantity.amount > 0, "must burn positive quantity" ); - check( quantity.amount == tokenIds.size(), "mismatch between burn amount and token info" ); - - stats statstable( _self, sym.code().raw() ); - auto existing_st = statstable.find( sym.code().raw() ); - check( existing_st != statstable.end(), "symbol does not exist at stats" ); - const auto& st = *existing_st; - - tokens tokenstable( _self, sym.code().raw() ); - for( auto const& tokenId : tokenIds ) { - auto it = tokenstable.find( tokenId ); - check( it != tokenstable.end(), "token with symbol does not exists" ); - check( it->owner == owner, "not the owner of token" ); - tokenstable.erase( it ); - } - - sub_balance( owner, quantity ); - statstable.modify( st, same_payer, [&]( auto& s ) { - s.supply -= quantity; - }); -} - -ACTION erc721::burnfrom( name burner, string sym, id_type tokenId, string memo ) { - require_auth( burner ); - - asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); - auto symbol = as.symbol; - check( symbol.is_valid(), "invalid symbol name" ); - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - stats statstable( _self, symbol.code().raw() ); - auto existing_st = statstable.find( symbol.code().raw() ); - check( existing_st != statstable.end(), "symbol does not exist at stats" ); - const auto& st = *existing_st; - - tokens tokenstable( _self, symbol.code().raw() ); - auto it = tokenstable.find( tokenId ); - check( it != tokenstable.end(), "token with symbol does not exists" ); - - require_recipient( it->owner ); - - check( burner == it->spender, "burner is not token spender" ); - - asset unit( 1, symbol ); - - sub_balance( it->owner, unit ); - - tokenstable.erase( it ); - statstable.modify( st, same_payer, [&]( auto& s ) { - s.supply -= unit; - }); -} - -ACTION erc721::transfer( name from, name to, string sym, id_type tokenId, string memo ) { - check( from != to, "cannot transfer to self" ); - require_auth( from ); - check( is_account( to ), "to account does not exist" ); - - require_recipient( from ); - require_recipient( to ); - - asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); - auto symbol = as.symbol; - - check( symbol.is_valid(), "invalid symbol name" ); - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - tokens tokenstable( _self, symbol.code().raw() ); - auto it = tokenstable.find( tokenId ); - check( it != tokenstable.end(), "token with symbol does not exists" ); - check( from == it->owner, "not the owner of token" ); - - auto payer = has_auth( to ) ? to : from; - - tokenstable.modify( it, payer, [&]( auto& token ) { - token.owner = to; - token.spender = to; - }); - - asset unit( 1, symbol ); - - sub_balance( from, unit ); - add_balance( to, unit, payer ); -} - -ACTION erc721::approve( name owner, name spender, string sym, id_type tokenId ) { - require_auth( owner ); - - asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); - auto symbol = as.symbol; - - check( symbol.is_valid(), "invalid symbol name" ); - - tokens tokenstable( _self, symbol.code().raw() ); - auto it = tokenstable.find( tokenId ); - check( it != tokenstable.end(), "token with symbol does not exists" ); - check( owner == it->owner, "not the owner of token" ); - - tokenstable.modify( it, same_payer, [&]( auto& token ) { - token.spender = spender; - }); -} - -ACTION erc721::transferfrom( name spender, name to, string sym, id_type tokenId, string memo ) { - require_auth( spender ); - - check( is_account( to ), "to account does not exist"); - - asset as(0, symbol( symbol_code( sym.c_str() ), 0) ); - auto symbol = as.symbol; - - check( symbol.is_valid(), "invalid symbol name" ); - check( memo.size() <= 256, "memo has more than 256 bytes" ); - - tokens tokenstable( _self, symbol.code().raw() ); - auto it = tokenstable.find( tokenId ); - check( it != tokenstable.end(), "token with symbol does not exists" ); - check( spender == it->spender, "spender is not token spender" ); - name owner = it->owner; - - require_recipient( owner ); - require_recipient( to ); - - auto payer = has_auth( to ) ? to : spender; - - tokenstable.modify( it, payer, [&]( auto& token ) { - token.owner = to; - token.spender = to; - }); - - asset unit( 1, symbol ); - - sub_balance( owner, unit ); - add_balance( to, unit, payer ); -} - -void erc721::sub_balance( name owner, asset value ) { - accounts from_acnts( get_self(), owner.value ); - - const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" ); - check( from.balance.amount >= value.amount, "overdrawn balance" ); - - name payer = !has_auth(owner) ? same_payer : owner; - - from_acnts.modify( from, payer, [&]( auto& a ) { - a.balance -= value; - }); -} - -void erc721::add_balance( name owner, asset value, name ram_payer ) { - accounts to_acnts( get_self(), owner.value ); - auto to = to_acnts.find( value.symbol.code().raw() ); - if( to == to_acnts.end() ) { - to_acnts.emplace( ram_payer, [&]( auto& a ){ - a.balance = value; - }); - } else { - to_acnts.modify( to, same_payer, [&]( auto& a ) { - a.balance += value; - }); - } -} \ No newline at end of file diff --git a/eos/erc721/erc721.hpp b/eos/erc721/erc721.hpp deleted file mode 100644 index 496df84..0000000 --- a/eos/erc721/erc721.hpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2019 DE-labtory - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -using namespace eosio; -using std::string; -using std::vector; -typedef uint64_t id_type; - -CONTRACT erc721 : public contract { - public: - erc721( name self, name first_receiver, datastream ds ) - : contract( self, first_receiver, ds ) {} - - ACTION create( name issuer, string sym ); - - ACTION issue( name to, asset quantity, vector>> tokenInfos, string memo ); - - ACTION burn( name owner, asset quantity, vector tokenIds, string memo ); - - ACTION burnfrom( name burner, string sym, id_type tokenId, string memo ); - - ACTION transfer( name from, name to, string sym, id_type tokenId, string memo ); - - ACTION approve( name owner, name spender, string sym, id_type tokenId ); - - ACTION transferfrom( name spender, name to, string sym, id_type tokenId, string memo ); - - static asset get_supply( name token_contract_account, symbol_code sym_code ) { - stats statstable( token_contract_account, sym_code.raw() ); - const auto& st = statstable.get( sym_code.raw() ); - return st.supply; - } - - static asset get_balance( name token_contract_account, name owner, symbol_code sym_code ) { - accounts accountstable( token_contract_account, owner.value ); - const auto& ac = accountstable.get( sym_code.raw() ); - return ac.balance; - } - - private: - TABLE account { - asset balance; - - uint64_t primary_key() const { return balance.symbol.code().raw(); } - }; - - TABLE currency_stat { - asset supply; - name issuer; - - uint64_t primary_key() const { return supply.symbol.code().raw(); } - uint64_t get_issuer() const { return issuer.value; } - }; - - TABLE token { - id_type tokenId; // Unique 64 bit identifier, - symbol sym; // token symbol - name owner; // token owner - asset value; // token value - name tokenName; // token name - name spender; // token spender - - id_type primary_key() const { return tokenId; } - uint64_t get_owner() const { return owner.value; } - }; - - typedef eosio::multi_index<"accounts"_n, account> accounts; - - typedef eosio::multi_index<"stat"_n, currency_stat, - indexed_by< "byissuer"_n, const_mem_fun< currency_stat, uint64_t, ¤cy_stat::get_issuer> > - > stats; - - typedef eosio::multi_index<"token"_n, token, - indexed_by< "byowner"_n, const_mem_fun< token, uint64_t, &token::get_owner> > - > tokens; - - void sub_balance(name owner, asset value); - void add_balance(name owner, asset value, name ram_payer); -}; \ No newline at end of file