Skip to content

Instantly share code, notes, and snippets.

@leordev
Last active September 16, 2018 19:35
Show Gist options
  • Save leordev/c927cd919f3a84be368b2bc5e2aee883 to your computer and use it in GitHub Desktop.
Save leordev/c927cd919f3a84be368b2bc5e2aee883 to your computer and use it in GitHub Desktop.
Pets Smart Contract
/**
* Pet-Tamagotchi-Alike Smart Contract
*
* The idea is to copy the original tamagotchi to the chain :)
*
*
* @author Leo Ribeiro
*/
#include <eosiolib/asset.hpp>
#include <eosiolib/eosio.hpp>
#include <eosiolib/transaction.hpp>
#include <eosiolib/singleton.hpp>
using namespace eosio;
using std::string;
using std::hash;
/* ****************************************** */
/* ------------ Types Declarations ---------- */
/* ****************************************** */
typedef uint64_t uuid;
constexpr uint8_t PET_TYPES = 109;
constexpr uint32_t DAY = 86400;
constexpr uint32_t HOUR = 3600;
constexpr uint8_t MAX_HEALTH = 100;
constexpr uint32_t HUNGER_TO_ZERO = 10 * HOUR;
constexpr uint32_t MIN_HUNGER_INTERVAL = 3 * HOUR;
constexpr uint8_t MAX_HUNGER_POINTS = 100;
constexpr uint8_t HUNGER_HP_MODIFIER = 1;
constexpr uint32_t HAPPINESS_TO_ZERO = 20 * HOUR;
constexpr uint8_t MAX_HAPPINESS_POINTS = 100;
constexpr uint8_t HAPPINESS_HP_MODIFIER = 2;
constexpr uint32_t AWAKE_TO_ZERO = 20 * HOUR;
constexpr uint32_t SLEEP_TO_ZERO = 8 * HOUR;
constexpr uint32_t MIN_AWAKE_INTERVAL = 8 * HOUR;
constexpr uint32_t MIN_SLEEP_PERIOD = 4 * HOUR;
constexpr uint8_t MAX_AWAKE_POINTS = 100;
constexpr uint8_t AWAKE_HP_MODIFIER = 2;
constexpr uint32_t CLEAN_TO_ZERO = 24 * HOUR;
constexpr uint8_t MAX_CLEAN_POINTS = 100;
constexpr uint8_t CLEAN_HP_MODIFIER = 3;
/* ****************************************** */
/* ------------ Contract Definition --------- */
/* ****************************************** */
class pet : public eosio::contract {
public:
pet(account_name self)
:eosio::contract(self),
pets(_self,_self),
pet_config(_self,_self)
{}
/* ****************************************** */
/* ------------ Contract Actions ------------ */
/* ****************************************** */
void createpet(name owner,
string pet_name) {
require_auth(owner);
st_pet_config pc = _get_pet_config();
print("creating pet");
uuid new_id = _next_id();
// creates the pet
pets.emplace(_self, [&](auto &r) {
st_pets pet{};
pet.id = new_id;
pet.name = pet_name;
pet.owner = owner;
pet.created_at = now();
pet.last_fed_at = pet.created_at;
pet.last_play_at = pet.created_at;
pet.last_bed_at = pet.created_at;
pet.last_shower_at = pet.created_at;
pet.type = (_hash_str(pet_name) + pet.created_at + pet.id + owner) % PET_TYPES;
r = pet;
});
// first update in the next minute
transaction update{};
update.actions.emplace_back(
permission_level{_self, N(active)},
_self, N(updatepet),
std::make_tuple(new_id, 1)
);
update.delay_sec = 60;
update.send(new_id, _self);
}
void updatepet(uuid pet_id, uint32_t iteration) {
require_auth(_self);
print(pet_id, "|", iteration, ": updating pet ");
auto itr_pet = pets.find(pet_id);
eosio_assert(itr_pet != pets.end(), "E404|Invalid pet");
st_pets pet = *itr_pet;
_update(pet);
pets.modify(itr_pet, 0, [&](auto &r) {
r.health = pet.health;
r.death_at = pet.death_at;
r.hunger = pet.hunger;
r.awake = pet.awake;
r.happiness = pet.happiness;
r.clean = pet.clean;
});
// recursive infinite update if not dead, each minute
uint32_t new_iteration = iteration + 1;
uint64_t new_trx_id = (pet_id << 32 | new_iteration);
transaction update{};
update.actions.emplace_back(
permission_level{_self, N(active)},
_self, N(updatepet),
std::make_tuple(pet_id, new_iteration)
);
update.delay_sec = 60;
update.send(new_trx_id, _self);
}
void feedpet(uuid pet_id) {
auto itr_pet = pets.find(pet_id);
eosio_assert(itr_pet != pets.end(), "E404|Invalid pet");
st_pets pet = *itr_pet;
_update(pet);
pets.modify(itr_pet, 0, [&](auto &r) {
r.health = pet.health;
r.death_at = pet.death_at;
r.hunger = pet.hunger;
r.awake = pet.awake;
r.happiness = pet.happiness;
r.clean = pet.clean;
uint32_t current_time = now();
bool can_eat = (current_time - pet.last_fed_at) > MIN_HUNGER_INTERVAL && r.is_sleeping == 0;
bool is_alive = r.health > 0;
if (can_eat && is_alive) {
r.health = MAX_HEALTH;
r.hunger = MAX_HUNGER_POINTS;
r.last_fed_at = now();
} else if (r.is_sleeping > 0) {
print("I111|Zzzzzzzz...");
} else if (!can_eat) {
print("I110|Not hungry");
} else if(!is_alive) {
print("I199|Dead don't feed");
}
});
}
void bedpet(uuid pet_id) {
auto itr_pet = pets.find(pet_id);
eosio_assert(itr_pet != pets.end(), "E404|Invalid pet");
st_pets pet = *itr_pet;
require_auth(pet.owner);
_update(pet);
pets.modify(itr_pet, 0, [&](auto &r) {
r.health = pet.health;
r.death_at = pet.death_at;
r.hunger = pet.hunger;
r.awake = pet.awake;
r.happiness = pet.happiness;
r.clean = pet.clean;
uint32_t current_time = now();
bool can_sleep = (current_time - pet.last_awake_at) > MIN_AWAKE_INTERVAL && r.is_sleeping == 0;
bool is_alive = r.health > 0;
if (can_sleep && is_alive) {
r.is_sleeping = 1;
r.last_bed_at = now();
} else if (!can_sleep) {
print("I201|Not now sir!");
} else if(!is_alive) {
print("I299|Dead don't sleep");
}
});
}
void awakepet(uuid pet_id) {
auto itr_pet = pets.find(pet_id);
eosio_assert(itr_pet != pets.end(), "E404|Invalid pet");
st_pets pet = *itr_pet;
require_auth(pet.owner);
_update(pet);
pets.modify(itr_pet, 0, [&](auto &r) {
r.health = pet.health;
r.death_at = pet.death_at;
r.hunger = pet.hunger;
r.awake = pet.awake;
r.happiness = pet.happiness;
r.clean = pet.clean;
uint32_t current_time = now();
bool can_awake = (current_time - pet.last_bed_at) > MIN_SLEEP_PERIOD && r.is_sleeping == 1;
bool is_alive = r.health > 0;
if (can_awake && is_alive) {
r.is_sleeping = 0;
r.last_awake_at = now();
r.awake = MAX_AWAKE_POINTS;
} else if (!can_awake) {
print("I301|Zzzzzzz");
} else if(!is_alive) {
print("I399|Dead don't awake");
}
});
}
void playpet(uuid pet_id) {
print("play lazy developer");
}
void washpet(uuid pet_id) {
print("wash lazy developer");
}
void transfer(uint64_t sender, uint64_t receiver) {
print("\n>>> sender >>>", sender, " - name: ", name{sender});
print("\n>>> receiver >>>", receiver, " - name: ", name{receiver});
// ??? Don't need to verify because we already did it in EOSIO_ABI_EX ???
// eosio_assert(code == N(eosio.token), "I reject your non-eosio.token deposit");
auto transfer_data = unpack_action_data<st_transfer>();
if(transfer_data.from == _self || transfer_data.to != _self) {
return;
}
print("\n>>> transfer data quantity >>> ", transfer_data.quantity);
eosio_assert(transfer_data.quantity.symbol == string_to_symbol(4, "EOS"),
"MonsterEOS only accepts EOS for deposits");
eosio_assert(transfer_data.quantity.is_valid(), "Invalid token transfer");
eosio_assert(transfer_data.quantity.amount > 0, "Quantity must be positive");
_tb_balances balances(_self, transfer_data.from);
asset new_balance;
auto itr_balance = balances.find(transfer_data.quantity.symbol);
if(itr_balance != balances.end()) {
balances.modify(itr_balance, transfer_data.from, [&](auto& r){
// Assumption: total currency issued by eosio.token will not overflow asset
r.funds += transfer_data.quantity;
new_balance = r.funds;
});
} else {
balances.emplace(transfer_data.from, [&](auto& r){
r.funds = transfer_data.quantity;
new_balance = r.funds;
});
}
print("\n", name{transfer_data.from}, " deposited: ", transfer_data.quantity);
print("\n", name{transfer_data.from}, " funds available: ", new_balance);
}
private:
struct st_transfer {
account_name from;
account_name to;
asset quantity;
string memo;
};
/* ****************************************** */
/* ------------ Contract Tables ------------- */
/* ****************************************** */
// @abi table pets i64
struct st_pets {
uuid id;
name owner;
string name;
uint8_t type;
uint32_t created_at;
uint32_t death_at = 0;
uint8_t health = MAX_HEALTH;
uint8_t hunger = MAX_HUNGER_POINTS;
uint32_t last_fed_at;
uint8_t awake = MAX_AWAKE_POINTS;
uint32_t last_bed_at;
uint32_t last_awake_at = 0;
uint8_t is_sleeping = 1;
uint8_t happiness = MAX_HAPPINESS_POINTS;
uint32_t last_play_at;
uint8_t clean = MAX_CLEAN_POINTS;
uint32_t last_shower_at;
uint64_t primary_key() const { return id; }
};
typedef multi_index<N(pets), st_pets> _tb_pet;
_tb_pet pets;
// @abi table balances i64
struct st_balance {
asset funds;
uint64_t primary_key() const { return funds.symbol; }
};
typedef multi_index<N(balances), st_balance> _tb_balances;
/* ****************************************** */
/* ------------ Contract Config Data -------- */
/* ****************************************** */
struct st_pet_config {
uuid last_id = 1000000000;
asset creation_fee = asset{10000,S(4,EOS)};
};
typedef singleton<N(pet_config), st_pet_config> pet_config_singleton;
pet_config_singleton pet_config;
/* ****************************************** */
/* ------------ Private Functions ----------- */
/* ****************************************** */
st_pet_config _get_pet_config(){
st_pet_config pc;
if (pet_config.exists()) {
pc = pet_config.get();
} else {
pc = st_pet_config{};
pet_config.set(pc, _self);
}
return pc;
}
uuid _next_id(){
st_pet_config pc = _get_pet_config();
pc.last_id++;
pet_config.set(pc, _self);
return pc.last_id;
}
uint64_t _hash_str(const string &str) {
return hash<string>{}(str);
}
void _update(st_pets &pet) {
eosio_assert(pet.health > 0 && pet.death_at == 0, "E099|Pet is dead");
uint32_t current_time = now();
uint8_t effect_hp_hunger = _calc_hunger_hp(pet, current_time);
int8_t hp = MAX_HEALTH - effect_hp_hunger;
if (hp <= 0) {
pet.health = 0;
pet.death_at = current_time;
} else {
pet.health = hp;
}
}
uint8_t _calc_hunger_hp(st_pets &pet, const uint32_t &current_time) {
// how long it's hungry?
uint32_t hungry_seconds = current_time - pet.last_fed_at;
uint8_t hungry_points = (uint8_t) (hungry_seconds * MAX_HUNGER_POINTS / HUNGER_TO_ZERO);
// calculates the effective hunger on hp, if pet hunger is 0
uint8_t effect_hp_hunger = 0;
if (hungry_points < MAX_HUNGER_POINTS) {
pet.hunger = MAX_HUNGER_POINTS - hungry_points;
} else {
effect_hp_hunger = (uint8_t) ((hungry_points - MAX_HUNGER_POINTS) / HUNGER_HP_MODIFIER);
pet.hunger = 0;
}
return effect_hp_hunger;
}
};
// EOSIO_ABI(pet, (createpet)(updatepet)(feedpet)(bedpet)(awakepet)(playpet)(washpet)(transfer))
// extend from EOSIO_ABI
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
extern "C" { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
if( action == N(onerror)) { \
/* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
} \
auto self = receiver; \
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
TYPE thiscontract( self ); \
switch( action ) { \
EOSIO_API( TYPE, MEMBERS ) \
} \
/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
} \
} \
}
EOSIO_ABI_EX(pet, (createpet)(updatepet)(feedpet)(bedpet)(awakepet)(playpet)(washpet)(transfer))
@leordev
Copy link
Author

leordev commented Jun 7, 2018

Thanks to this awesome reply https://eosio.stackexchange.com/questions/421/how-to-do-something-when-your-contract-is-an-action-notification-recipient-like I could implement the EOSIO_ABI_EX.

Downside to this approach is that, as for today's date, we need to uncomment line 411 (EOSIO_ABI macro) and comment the whole EOSIO_ABI_EX to generate the ABIs, and after that comment EOSIO_ABI macro again and uncomment the EOSIO_ABI_EX to generate my contract for deployment.

Copy link

ghost commented Jul 13, 2018

Thanks for the peace of code!
To not bother yourself with this comments trick you can try using conditional compilation - it really works for me:

#if defined(ABIGEN_MODE)
EOSIO_ABI(pet, (createpet)(updatepet)(feedpet)(bedpet)(awakepet)(playpet)(washpet)(transfer))
#elif defined(COMP_MODE)
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      if( action == N(onerror)) { \
         /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
         eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
      } \
      auto self = receiver; \
      if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
}
EOSIO_ABI_EX(pet, (createpet)(updatepet)(feedpet)(bedpet)(awakepet)(playpet)(washpet)(transfer))
#else
#error "Wrong mode!"
#endif

And add something like -DCOMP_MODE to the compiler options, and -extra-arg=-DABIGEN_MODE to the abigen options inside your makefile.

@86chenjie
Copy link

@IvanYakimov i try this

eosiocpp -o ./ctr/ctr.wast ./ctr/ctr.cpp -D COMP_MODE=1
eosiocpp -g ./ctr/ctr.abi ./ctr/ctr.cpp -D ABIGEN_MODE=1

not work. can you paste your solution? thanks.

@smlu
Copy link

smlu commented Aug 8, 2018

You can simplify Ivan's solution by redefining the EOSIO_ABI macro.
This way eosio abi generator will automatically pick up and generate correct abi file.

Example:

#undef EOSIO_ABI
#define EOSIO_ABI( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      if( action == N(onerror)) { \
         /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
         eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
      } \
      auto self = receiver; \
      if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
}

EOSIO_ABI(pet, (createpet)(updatepet)(feedpet)(bedpet)(awakepet)(playpet)(washpet)(transfer))

@86chenjie
Copy link

@smlu works perfect. thanks!

@abhi3700
Copy link

update the EOS_ABI code as updated in this link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment