Notifications
This contract adds the following capabilities to the previous examples:
- Receives notifications from
eosio.token
and tracks user balances - Deducts from the user balance whenever the user buys a dog
This example does not cover:
- Removing empty balance records
- Returning excess funds to users
- Protecting against dust attacks on the balance table
- Treating incoming funds from system accounts as special (e.g. unstaking, selling rex, selling ram)
Place notify.cpp
and CMakeLists.txt
in an empty folder.
notify.cpp
#include <eosio/asset.hpp>
#include <eosio/eosio.hpp>
namespace example
{
// Keep track of deposited funds
struct balance
{
eosio::name owner;
eosio::asset balance;
uint64_t primary_key() const { return owner.value; }
};
EOSIO_REFLECT(balance, owner, balance)
typedef eosio::multi_index<"balance"_n, balance> balance_table;
// A purchased animal
struct animal
{
eosio::name name;
eosio::name type;
eosio::name owner;
eosio::asset purchase_price;
uint64_t primary_key() const { return name.value; }
};
EOSIO_REFLECT(animal, name, type, owner, purchase_price)
typedef eosio::multi_index<"animal"_n, animal> animal_table;
struct example_contract : public eosio::contract
{
using eosio::contract::contract;
// eosio.token transfer notification
void notify_transfer(eosio::name from,
eosio::name to,
const eosio::asset& quantity,
std::string memo)
{
// Only track incoming transfers
if (from == get_self())
return;
// The dispatcher has already checked the token contract.
// We need to check the token type.
eosio::check(quantity.symbol == eosio::symbol{"EOS", 4},
"This contract does not deal with this token");
// Record the change
add_balance(from, quantity);
}
// Action: user buys a dog
void buydog(eosio::name user, eosio::name dog, const eosio::asset& price)
{
require_auth(user);
eosio::check(price.symbol == eosio::symbol{"EOS", 4},
"This contract does not deal with this token");
eosio::check(price.amount >= 50'0000, "Dogs cost more than that");
sub_balance(user, price);
animal_table table{get_self(), get_self().value};
table.emplace(user, [&](auto& record) {
record.name = dog;
record.type = "dog"_n;
record.owner = user;
record.purchase_price = price;
});
}
// This is not an action; it's a function internal to the contract
void add_balance(eosio::name owner, const eosio::asset& quantity)
{
balance_table table(get_self(), get_self().value);
auto record = table.find(owner.value);
if (record == table.end())
table.emplace(get_self(), [&](auto& a) {
a.owner = owner;
a.balance = quantity;
});
else
table.modify(record, eosio::same_payer, [&](auto& a) { a.balance += quantity; });
}
// This is not an action; it's a function internal to the contract
void sub_balance(eosio::name owner, const eosio::asset& quantity)
{
balance_table table(get_self(), get_self().value);
const auto& record = table.get(owner.value, "user does not have a balance");
eosio::check(record.balance.amount >= quantity.amount, "not enough funds deposited");
table.modify(record, owner, [&](auto& a) { a.balance -= quantity; });
}
};
EOSIO_ACTIONS(example_contract,
"example"_n,
notify("eosio.token"_n, transfer), // Hook up notification
action(buydog, user, dog, price))
} // namespace example
EOSIO_ACTION_DISPATCHER(example::actions)
EOSIO_ABIGEN(actions(example::actions),
table("balance"_n, example::balance),
table("animal"_n, example::animal))
Additional files
Building
This will create notify.wasm
and notify.abi
:
mkdir build
cd build
cmake `clsdk-cmake-args` ..
make -j $(nproc)
Trying the contract
# Create some users
cleos create account eosio alice EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
cleos create account eosio bob EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
# Set up eosio.token
# Note: the build system created a symlink to clsdk for easy access to the token contract
cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
cleos set abi eosio.token clsdk/contracts/token.abi
cleos set code eosio.token clsdk/contracts/token.wasm
cleos push action eosio.token create '["eosio", "1000000000.0000 EOS"]' -p eosio.token
cleos push action eosio.token issue '["eosio", "1000000000.0000 EOS", ""]' -p eosio
cleos push action eosio.token open '["alice", "4,EOS", "alice"]' -p alice
cleos push action eosio.token open '["bob", "4,EOS", "bob"]' -p bob
cleos push action eosio.token transfer '["eosio", "alice", "10000.0000 EOS", "have some"]' -p eosio
cleos push action eosio.token transfer '["eosio", "bob", "10000.0000 EOS", "have some"]' -p eosio
# Install the contract
cleos create account eosio notify EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
cleos set abi notify notify.abi
cleos set code notify notify.wasm
# Try out the contract
cleos push action eosio.token transfer '["alice", "notify", "300.0000 EOS", "for purchases"]' -p alice
cleos push action eosio.token transfer '["bob", "notify", "300.0000 EOS", "for purchases"]' -p bob
cleos push action notify buydog '["alice", "fido", "100.0000 EOS"]' -p alice
cleos push action notify buydog '["alice", "rex", "120.0000 EOS"]' -p alice
cleos push action notify buydog '["bob", "lambo", "70.0000 EOS"]' -p bob
# See the remaining balances and the purchased animals
cleos get table notify notify balance
cleos get table notify notify animal