UPDATE:
Updated the code to compile with eosio.cdt
version 1.6.1.
I finally got it working nicely! Parsing the token symbol in the action parameters was especially complicated.
The other solution posted has a couple of issues that will prevent you from clearing all the RAM properly:
- It doesn't delete the
stat
table, leaving there some info about the created token using up some memory.
- You're supposed to hand it an arbitrary amount in the action parameters, which has to match the same exact decimal precision used when creating the token, instead of just specifying the symbol of the token you want to delete.
My final solution is thoroughly tested by creating and issuing various tokens using the standard eosio.token
contract, then replacing it by this one, and using the new actions to destroy the database records. I checked every step by inspecting the tables associated to the contract account using
cleos get table <CONTRACT_ACCOUNT> <TOKEN_NAME> stat
and
cleos get table <CONTRACT_ACCOUNT> <AIRDROPPED_ACCOUNT> accounts
and everything was neatly cleared at the end. You will need the list of accounts that you airdropped to, but you won't need the exact amounts that they are currently holding.
The code goes like this:
token_ram_recovery.cpp
#include <eosio/asset.hpp>
#include <eosio/eosio.hpp>
#include <string>
using namespace eosio;
using std::string;
class[[eosio::contract]] token : public contract
{
private:
struct [[eosio::table]] account
{
asset balance;
uint64_t primary_key() const { return balance.symbol.code().raw(); }
};
struct [[eosio::table]] currency_stats
{
asset supply;
asset max_supply;
name issuer;
uint64_t primary_key() const { return supply.symbol.code().raw(); }
};
typedef eosio::multi_index<name("accounts"), account> accounts;
typedef eosio::multi_index<name("stat"), currency_stats> stats;
public:
using contract::contract;
[[eosio::action]] void destroytoken(string symbol) {
require_auth(_self);
symbol_code sym(symbol);
stats stats_table(_self, sym.raw());
auto existing = stats_table.find(sym.raw());
check(existing != stats_table.end(), "Token with symbol does not exist");
stats_table.erase(existing);
};
[[eosio::action]] void destroyacc(string symbol, name account) {
require_auth(_self);
symbol_code sym(symbol);
accounts accounts_table(_self, account.value);
const auto &row = accounts_table.get(sym.raw(), "No balance object found for provided account and symbol");
accounts_table.erase(row);
};
};
EOSIO_DISPATCH(token, (destroytoken)(destroyacc))
The code is well annotated so you can generate the .abi
automatically when compiling the code using eosio-cpp -abigen -contract token token_ram_recovery.cpp -o token_ram_recovery.wasm
. Then just deploy this new contract to the same account that had the previous one, and proceed with the deletion of the token and the accounts:
Destroy the token from the stat
table:
cleos push action <CONTRACT_ACCOUNT> destroytoken '["<TOKEN_SYMBOL>"]' -p <CONTRACT_ACCOUNT>@active
Destroy each account that holds any of the token:
cleos push action <CONTRACT_ACCOUNT> destroyacc '["<TOKEN_SYMBOL>", "<AIRDROPPED_ACCOUNT>"]' -p <CONTRACT_ACCOUNT>@active
Let me know if you need any more assistance. I hope everything goes well!