Skip to content

Commit

Permalink
Bugfixes for compiling; updates to tests; expanding docs
Browse files Browse the repository at this point in the history
  • Loading branch information
kdmukai committed Jul 8, 2019
1 parent 3ce079b commit d6fd9a7
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 618 deletions.
40 changes: 20 additions & 20 deletions README.md
Expand Up @@ -9,46 +9,46 @@ An open platform for Achievements/Badges/Trophies that will be preserved forever
Current achievement systems are completely siloed in their own ecosystems--XBox gamertag, individual mobile games, Steam trophies, even certifications for tech or skills training (e.g. Khan Academy badges). Because of this they all suffer from varying levels of impermanence and fragility. I can work hard to unlock a new badge in my running app ("50-Mile Club!"), but if that service shuts down, all of my badges go with it.

### Achievements should be accessible outside of their ecosystem
Even if there was a sense of reasonable permanence for my achievements--XBox Live probably isn't going to disappear tomorrow--I shouldn't have to go to each individual ecosystem or sign into each individual app to see them. But there's currently no way to view my accomplishments from Steam alongside all my mobile game achievements and everywhere else all in one place.
I shouldn't have to go to each individual ecosystem or sign into each individual app to see and share my achievements. But there's currently no way to view my accomplishments from Steam alongside all my mobile game achievements and everywhere else all in one place.

### Enter the blockchain
Blockchains are as close to immutable, permanent digital storage as we're ever going to get. Writing achievements to the blockchain will preserve them regardless of what happens to the organization or company that originally granted the achievement/trophy/badge. And once they're written to a public blockchain it's simple to view all of them in one grand trophy room, share any of them out to social media, etc. Permanence + unified public access.
Blockchains are as close to immutable, permanent digital storage as we're going to get. Writing achievements to the blockchain will preserve them regardless of what happens to the organization or company that originally granted the achievement/trophy/badge. And once they're written to a public blockchain it's simple to view all of them in one grand trophy room, share any of them out to social media, etc. Permanence + unified public access.

## Achieveos overview
Achieveos is an EOS smart contract that provides a simple, open platform for any permanent achievement system to be built upon. It is structured as a series of simple tables that have database-like relationships.
Achieveos is an EOS smart contract that provides a simple, open platform for any permanent achievement system to be built upon.

But because smart contracts are cumbersome to directly interact with, it's expected that richer web apps will be built on top of the Achieveos functionality. Think of Achieveos as the backend database.
But because smart contracts are cumbersome to directly interact with, it's expected that richer web apps will be built on top of the Achieveos functionality. Think of Achieveos as a kind of services-rich backend database (AaaS? Achievements as a Service).

The first demonstration web app, eternalbadge.com (coming soon), will be an example of how the Achieveos smart contract can be used. The smart contract details will be totally hidden away from the users; they won't need to know anything about blockchains to be able to use the site. But crucially, should eternalbadge.com ever shut down, all of the achievements managed through the site will live on forever on the EOS blockchain.

It's the best of both worlds: A user-friendly UI but with permanently accessible, non-siloed data.

In fact, a future site could leverage the exact same data and provide a new UI. Users could pick up right where they left off (well, sort of) and carry on in this "new" world.

### Structure
You start by creating your **Organization** which will define its own achievements and grant them to its members or users. It could be a new gaming app where players can unlock trophies for completing challenges. Or a local sports team that will track its season records. Or a school's National Honor Society that will log its new inductees and other honors.
### Easy onboarding; "custodial" achievements
A big hurdle with blockchain-based users is the overly complex onboarding process: converting fiat to crypto; setting up access to those funds via tools like Metamask, Scatter, etc; generating and signing transactions to interact with a smart contract. This is just not a reasonable expectation yet for the vast majority of users.

Next you define whatever achievement **Categories** make sense for your Organization. The football team might want to separate their achievements for the season by "Offense", "Defense", and "Special Teams" records.
So instead each project can add their own users as simple `string` blockchain data (`name=Keith01`, `userid=your_internal_id_1234`) and immediately start granting achievements to that user. The achievements are written to the blockchain but in this case the user has not yet asserted any control over their gamer identity nor claimed ownership of their achievements. In a sense their achievements are held _in custody_ on their behalf.

And finally you add **Achievements** within those Categories. "Longest Touchdown" would be an "Offense" record held by an individual wide receiver. "Most Sacks" would be a "Defense" season record. "Longest Field Goal" for "Special Teams".

Or for a gaming context maybe there's a "Challenge Badges" Category for achievements like "Killed 100 Beasts in 1 Round". The same game might also have a "League Trophies" Category for "Won an 8-Player Tournament".
### Claiming ownership; unify achievements
If a user has the interest and the savvy to create their own blockchain account, the system has a mechanism for them to claim their user identity in each vendor's achievement ecosystem.

### Blockchain storage is expensive
The structure above writes the bare minimum data to the blockchain to minimize expenses while still ensuring that the data is human-readable.
Once claimed it is then possible for the user to view all of their achievements across all their gaming ecosystems in one place via an achievement-aware block explorer.

Projects built on top of Achieveos are free to store more data via the optional `json_data` field in each struct. For example, you could include the url for an icon asset for a given Achievement and perhaps a longer description:

```
json_data = {
"icon": "https://somewhere/dir/someimage.png",
"details": "This honor is awarded to the individual who demonstrates..."
}
```
### Structure
You start by creating your **Organization** which will define its own achievements and grant them to its members or users.

Next you define whatever achievement **Categories** make sense for your game or app. Maybe you want to distinguish between "Trophies" vs "Badges" or "Solo" vs "Team" achievements.

Finally you add your **Achievements** within those Categories. "Ran 10 miles" could be a "Solo" training badge.

The additional RAM costs of that storage on the EOS blockchain is up to each project or user to manage.
### Note: Blockchain storage can be expensive
The structure above writes the bare minimum data to the blockchain to minimize expenses while still ensuring that the data is human-readable. Each Achievement does have an optional `description` field so you can provide more details about the accomplishment, but this is best kept as short as possible.

However, it likely makes more sense to take a hybrid approach: Store the bare minimum for posterity in Achieveos but have additional, richer data in a standard database.
### Assets
Images for each achievement are probably too much data to store on the blockchain. So instead each Organization specifies an `assetbaseurl` (e.g. "mydomainname.com/images/trophies") and then each Achievement has an `assetname` (e.g. "500_kills.png"). In this way we strike a compromise between providing nicely rendered achievement browsing without burdening game studios with excessive blockchain storage costs.


## Running locally
Expand Down
41 changes: 20 additions & 21 deletions achieveos.cpp
Expand Up @@ -136,10 +136,10 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
uint32_t category_id,
string achievement_name,
string description,
string asseturl) {
string assetname) {
check_is_contract_or_owner(org_owner);

validateAsseturl(asseturl);
validateAssetname(assetname);

auto orgs_table = get_orgs_table_for(org_owner);
auto orgs_iter = orgs_table.find(organization_id);
Expand All @@ -153,7 +153,7 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
Achievement achievement;
achievement.name = achievement_name;
achievement.description = description;
achievement.asseturl = asseturl;
achievement.assetname = assetname;
achievement.active = true;

orgs_table.modify(orgs_iter, maybe_charge_to(org_owner), [&](auto& org) {
Expand All @@ -162,8 +162,8 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
}


void validateAsseturl(string asseturl) {
check(asseturl.find(" ") == string::npos, "Invalid asseturl");
void validateAssetname(string assetname) {
check(assetname.find(" ") == string::npos, "Invalid assetname");
}


Expand All @@ -179,10 +179,10 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
uint32_t achievement_id,
string achievement_name,
string description,
string asseturl) {
string assetname) {
check_is_contract_or_owner(org_owner);

validateAsseturl(asseturl);
validateAssetname(assetname);

auto orgs_table = get_orgs_table_for(org_owner);
auto orgs_iter = orgs_table.find(organization_id);
Expand All @@ -204,7 +204,7 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
auto achievement = org.categories[category_id].achievements[achievement_id];
achievement.name = achievement_name;
achievement.description = description;
achievement.asseturl = asseturl;
achievement.assetname = assetname;
});
}

Expand Down Expand Up @@ -285,8 +285,8 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
Meant for possible GDPR, etc compliance issues.
**/
[[eosio::action]]
void wipeusername(name org_owner, uint32_t organization_id, uint32_t user_id) {
edituser(org_owner, organization_id, user_id, "");
void wipeusername(name org_owner, uint32_t organization_id, uint32_t user_id, string userid) {
edituser(org_owner, organization_id, user_id, "", userid);
}


Expand Down Expand Up @@ -321,24 +321,24 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
check(user_id < orgs_iter->users.size(), "User not found");

// Cannot claim an already claimed User
check(org.users[user_id].account == "", "User has already been claimed");
check(orgs_iter->users[user_id].account == "", "User has already been claimed");

orgs_table.modify(orgs_iter, maybe_charge_to(org_owner), [&](auto& org) {
org.users[user_id].account = user_account;
org.users[user_id].account = user_account.to_string();
});
}


/**
**/
void isapproved(name org_owner, uint32_t organization_id, uint32_t user_id) {
bool isapproved(name org_owner, uint32_t organization_id, uint32_t user_id) {
auto orgs_table = get_orgs_table_for(org_owner);
auto orgs_iter = orgs_table.find(organization_id);
check(orgs_iter != orgs_table.end(), "Organization not found");

check(user_id < orgs_iter->users.size(), "User not found");

return org.users[user_id].account != "";
return orgs_iter->users[user_id].account != "";
}


Expand All @@ -362,23 +362,22 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
check(user_id < orgs_iter->users.size(), "User not found");

// Claim must already be approved for this account
check(org.users[user_id].account == user_account, "User claim has not been approved");
check(orgs_iter->users[user_id].account == user_account.to_string(), "User claim has not been approved");

auto myachieveos_table = get_myachieveos_table_for(user_account);

// Make sure the user_account hasn't already claimed this User
for (auto iter = myachieveos_table.begin(); iter != myachieveos_table.end(); iter++) {
check(
iter->organization_id != organization_id &&
iter->user_id != user_id,
(iter->organization_id != organization_id && iter->user_id != user_id),
"Already claimed this User");
}

// Add the MyAchieveos entry in the account's namespace.
// user_account pays for RAM.
myachieveos_table.emplace(user_account, [&](auto& entry) {
entry.key = myachieveos_table.available_primary_key();
entry.org_owner = org_owner;
entry.org_owner = org_owner.to_string();
entry.organization_id = organization_id;
entry.user_id = user_id;
});
Expand Down Expand Up @@ -465,7 +464,7 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
struct User {
string name;
string userid; // org's internal identifier for this user; can be empty string.
name account; // The EOS account that has claimed this user entry, if any.
string account; // The EOS account that has claimed this user entry, if any. Must be a normal string rather than an eosio::name
map<uint32_t, UserAchievementsList> bycategory;
};

Expand Down Expand Up @@ -498,10 +497,10 @@ class [[eosio::contract("achieveos")]] achieveos : public eosio::contract {
uint32_t organization_id;
uint32_t user_id;
uint32_t primary_key() const { return key; }
}
};
typedef eosio::multi_index<"myachieveos"_n, MyAchieveos> myachieveos_multi_index; // EOS table names must be <= 12 chars

orgs_multi_index get_myachieveos_table_for(name account) {
myachieveos_multi_index get_myachieveos_table_for(name account) {
return myachieveos_multi_index(get_self(), account.value);
}

Expand Down

0 comments on commit d6fd9a7

Please sign in to comment.