The review model of the ConsenSys dapp store prototype: stores ratings and reviews of dapp versions.
Key Facts
Description
ReviewModel stores reviews in an EternalDB. A rating must be between 1 and 5, and a review refers to a developer and a dapp version.
Source Verified
Exact runtime and creation bytecode match with soljson v0.1.6+commit.d41f8b7c, optimizer on. Source: github.com/ConsenSysMesh/dapp-store-contracts @ 0850f857 (contracts/ReviewModel.sol, author original; import/import_headers inlined into one file, every inlined file verbatim). Verified on Etherscan. Not submitted to Sourcify: its oldest supported compiler is v0.1.7.
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
Frontier Era
The initial release of Ethereum. A bare-bones implementation for technical users.
Bytecode Overview
Verified Source Available
Source verified through compiler archaeology and exact bytecode matching.
View Verification ProofShow source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
contract EternalDB {
address public admin;
mapping (uint => uint[])[] data;
modifier restricted { if (msg.sender == admin) _ }
function EternalDB() {
admin = msg.sender;
data.length = 1; // 1-indexed, so things like mappings to ids will work
}
function transfer_ownership(address _admin) restricted {
admin = _admin;
}
function count() constant returns(uint) {
return data.length - 1;
}
function has(uint id) constant returns(bool) {
return id <= count();
}
function new_entry() restricted returns(uint) {
return data.length++;
}
function add(uint id, uint key, uint value) restricted {
uint[] attribute = data[id][key];
attribute[attribute.length++] = value;
}
function get_length(uint id, uint key) constant returns(uint) {
return data[id][key].length;
}
function get_entry(uint id, uint key, uint index) constant returns(uint) {
return data[id][key][index];
}
function get_all(uint id, uint key) constant returns(uint[]) {
return data[id][key];
}
function clear(uint id, uint key) restricted {
data[id][key].length = 0;
}
function delete_entry(uint id, uint key, uint index) restricted {
uint[] attribute = data[id][key];
for (uint i = index; i + 1 < attribute.length; i++) {
attribute[i] = attribute[i + 1];
}
attribute.length--;
}
}
contract AbstractModel {
function set_db(EternalDB _db);
function replace(address replacement);
}
contract ModelCoordinator {
mapping(uint => address) public models;
address public admin;
function ModelCoordinator() {
admin = msg.sender;
}
function register(uint name, AbstractModel model, EternalDB db) {
if (msg.sender == admin) {
if (models[name] != 0) {
AbstractModel old_model = AbstractModel(models[name]);
old_model.replace(model);
}
models[name] = address(model);
if (address(db) == 0) {
db = new EternalDB();
db.transfer_ownership(address(model));
}
model.set_db(db);
}
}
}
contract Model {
EternalDB public db;
uint[2][] public keys;
uint[2][] public associations;
uint[2][] public read_only;
ModelCoordinator public coordinator;
uint constant INTEGER = 0x696e7465676572; // "integer"
uint constant NUMBER = 0x6e756d626572; // "number"
uint constant ADDRESS = 0x61646472657373; // "address"
uint constant STRING = 0x737472696e67; // "string"
uint constant DATETIME = 0x6461746574696d65; // "datetime"
uint constant BOOL = 0x626f6f6c; // "bool"
uint constant INTEGER_ARRAY = 0x696e74656765725f6172726179; // "integer_array"
uint constant STRING_ARRAY = 0x737472696e675f6172726179; // "string_array"
// References are a single pointer to another model, and that
// model type is inferred by the key name. i.e., "version_id" -> VersionModel
// References should not be confused with associations. Associations
// have a "has_many" relationship whereas references point to a single instance.
// Note: All reference keys must end in "_id".
uint constant REFERENCE = 0x7265666572656e6365; // "reference"
// Used to designate polymorphic associations on your models.
// These are like references, except that the type is pulled from the
// associated key which has the polymorphic type specified. Note the
// id must come first in the keys list, and the type must follow
// immediately after the id.
uint constant POLYMORPHIC_REFERENCE = 0x706f6c796d6f72706869635f7265666572656e6365; // "polymorphic_reference"
uint constant POLYMORPHIC_TYPE = 0x706f6c796d6f72706869635f74797065; // "polymorphic_type"
// Constants used for saving the created and last udpated blocks for each entry.
// With these blocks, you can garner a lot of information about when the entry
// was created and updated, and what happened during.
uint constant CREATED_BLOCK = 0x637265617465645f626c6f636b; // "created_block"
uint constant LAST_UPDATED_BLOCK = 0x6c6173745f757064617465645f626c6f636b; // "last_updated_block"
uint constant OWNERS = 0x6f776e657273; // "owners"
// Constant for marking a model entry as destroyed.
uint constant DESTROYED = 0x64657374726f796564; // "destroyed"
// Note: If you change the signature of any of these events,
// the Javascript library needs to be updated accordingly.
event Log(string reason);
event Create(uint256 id);
event Update(uint256 id);
event Destroy(uint256 id);
event ValidationError(uint256 indexed key, string reason);
event GeneralError(string reason);
function Model() {
coordinator = ModelCoordinator(msg.sender);
}
function get_keys() constant returns(uint[2][]) {
return keys;
}
function get_read_only_keys() constant returns(uint[2][]) {
return read_only;
}
function get_associations() constant returns(uint[2][]) {
return associations;
}
function get_default_keys() returns(uint[2][4]) {
uint[2][4] memory defaults;
defaults[0][0] = CREATED_BLOCK;
defaults[0][1] = INTEGER;
defaults[1][0] = LAST_UPDATED_BLOCK;
defaults[1][1] = INTEGER;
defaults[2][0] = OWNERS;
defaults[2][1] = INTEGER_ARRAY; // Need to implement address arrays.
defaults[3][0] = DESTROYED;
defaults[3][1] = BOOL;
return defaults;
}
function set_db(EternalDB _db) {
if (address(db) == 0) {
db = _db;
}
}
function set_coordinator(ModelCoordinator _coordinator) {
if (msg.sender == address(coordinator)) {
coordinator = _coordinator;
}
}
function create(uint[101][] attributes) returns(uint) {
Log("create started");
if (validate(msg.sender, 0, attributes) == true) {
uint id = db.new_entry();
for (uint i = 0; i < keys.length; i++) {
for (uint j = 1; j <= attributes[i][0]; j++) {
db.add(id, keys[i][0], attributes[i][j]);
}
}
add_owner(id, msg.sender);
db.add(id, DESTROYED, 0); // false
db.add(id, CREATED_BLOCK, block.number);
db.add(id, LAST_UPDATED_BLOCK, block.number);
Create(id);
return id;
}
}
function update(uint id, uint[101][] attributes) returns(bool) {
if (validate(msg.sender, id, attributes) == true) {
for (uint i = 0; i < keys.length; i++) {
db.clear(id, keys[i][0]);
for (uint j = 1; j <= attributes[i][0]; j++) {
db.add(id, keys[i][0], attributes[i][j]);
}
}
db.clear(id, LAST_UPDATED_BLOCK);
db.add(id, LAST_UPDATED_BLOCK, block.number);
Update(id);
return true;
}
return false;
}
function has_owners(uint id) constant returns(bool) {
return db.get_length(id, OWNERS) != 0;
}
function is_owner(uint id, address to_check) constant returns(bool) {
for (var i = 0; i < db.get_length(id, OWNERS); i++) {
uint owner = db.get_entry(id, OWNERS, i);
if (address(owner) == to_check) {
return true;
}
}
return false;
}
function add_owner(uint id, address owner) {
if (has_owners(id) == false) {
db.add(id, OWNERS, uint(owner));
return;
}
if (is_owner(id, msg.sender)) {
db.add(id, OWNERS, uint(owner));
}
}
function destroy(uint id) {
if (is_owner(id, msg.sender)) {
db.clear(id, DESTROYED);
db.add(id, DESTROYED, 1); // true
Destroy(id);
}
}
function is_destroyed(uint id) constant returns(bool) {
return db.get_entry(id, DESTROYED, 0) == 1;
}
function validate(address sender, uint id, uint[101][] attributes) returns(bool) {
return true;
}
function get_blocks_for(uint id) constant returns (uint[2]) {
uint[2] memory blocks;
blocks[0] = db.get_entry(id, CREATED_BLOCK, 0);
blocks[1] = db.get_entry(id, LAST_UPDATED_BLOCK, 0);
return blocks;
}
// Example:
//
// id = id of this model (say, Dapp)
// association_name = VERSIONS (association on Dapp that holds the association)
// association_id = id of Version to associate with
//
// The association_type is inferred from the name and will be used to ensure
// the association is being set by the right model.
function add_to_association(uint id, uint association_name, uint association_id) {
if (db.has(id) == false) {
return;
}
uint association_type = 0x0;
// Find the type matching the association_name.
for (uint i = 0; i < associations.length; i++) {
if (associations[i][0] == association_name) {
association_type = associations[i][1];
break;
}
}
// Only accept associations from the type specified.
if (association_type == 0x0 || msg.sender != coordinator.models(association_type)) {
return;
}
// Since we found the type with the right name, then can just go ahead and add the association.
db.add(id, association_name, association_id);
// Update timestamp
db.clear(id, LAST_UPDATED_BLOCK);
db.add(id, LAST_UPDATED_BLOCK, block.number);
}
function set_reference(uint id, uint reference_name, uint reference_id) {
if (db.has(id) == false) {
return;
}
// Here, we use a little "hack" to get the reference type. Since
// the reference type is string data packed into an integer, we can
// "pop off" the "_id" part of the reference name using division,
// which will give us the type.
// Note: 16777216 is 16 ^ 6, or 3 ASCII characters.
uint reference_type = reference_name / 16777216;
if (msg.sender != coordinator.models(reference_type)) {
return;
}
// Since we found the type with the right name, then can just go ahead and set the reference.
db.clear(id, reference_name);
db.add(id, reference_name, reference_id);
// Update timestamp
db.clear(id, LAST_UPDATED_BLOCK);
db.add(id, LAST_UPDATED_BLOCK, block.number);
}
/*function paged(uint start_id, uint page_size) constant returns(uint[102]) {
uint[102] memory page;
if (page_size > 100) {
page_size = 100;
}
uint found = 0;
uint next = 0;
uint current_id = start_id;
while(db.has(current_id) && found < 100) {
if (is_destroyed(current_id) == false) {
found += 1;
next = current_id + 1;
page[found+1] = current_id;
}
current_id += 1;
}
if (db.has(next) == false) {
next = 0;
}
page[0] = found;
page[1] = next;
return page;
}*/
function replace(address replacement) {
if (msg.sender == address(coordinator)) {
db.transfer_ownership(replacement);
}
}
}
contract SimpleIndex {
mapping(uint => uint) data;
address public admin;
modifier restricted { if (msg.sender == admin) _ }
function SimpleIndex() {
admin = msg.sender;
}
function set(uint key, uint value) restricted {
data[key] = value;
}
function transfer_ownership(address _admin) restricted {
admin = _admin;
}
function lookup(uint key) returns(uint) {
return data[key];
}
}
contract DeveloperModel is Model {
SimpleIndex public address_index;
uint constant NAME = 0x6e616d65;
uint constant LOGO = 0x6c6f676f;
uint constant DESCRIPTION = 0x6465736372697074696f6e;
uint constant ADDRESS = 0x61646472657373;
uint constant DAPP = 0x64617070;
uint constant DAPPS = 0x6461707073;
uint constant ATTRIBUTE = 0x617474726962757465; // "attribute"
uint constant ATTRIBUTES = 0x61747472696275746573; // "attributes"
uint constant REVIEW = 0x726576696577; // "review"
uint constant REVIEWS = 0x72657669657773; // "reviews"
uint constant RATING_COUNT = 0x726174696e675f636f756e74;
uint constant RATING_TOTAL = 0x726174696e675f746f74616c;
function DeveloperModel(SimpleIndex _address_index) {
if (address(_address_index) == 0) {
_address_index = new SimpleIndex();
}
address_index = _address_index;
keys.length = 3;
// Names
keys[0][0] = NAME;
keys[1][0] = LOGO;
keys[2][0] = DESCRIPTION;
// Types
keys[0][1] = STRING;
keys[1][1] = STRING;
keys[2][1] = STRING;
associations.length = 3;
associations[0][0] = DAPPS; // name of association
associations[0][1] = DAPP; // type
associations[1][0] = ATTRIBUTES;
associations[1][1] = ATTRIBUTE;
associations[2][0] = REVIEWS;
associations[2][1] = REVIEW;
read_only.length = 3;
read_only[0][0] = ADDRESS;
read_only[1][0] = RATING_COUNT;
read_only[2][0] = RATING_TOTAL;
read_only[0][1] = ADDRESS;
read_only[1][1] = INTEGER;
read_only[2][1] = INTEGER;
}
function create(uint[101][] attributes) returns(uint) {
uint id = super.create(attributes);
if (id != 0) {
db.add(id, RATING_COUNT, 0);
db.add(id, RATING_TOTAL, 0);
db.add(id, ADDRESS, uint(msg.sender));
address_index.set(uint(msg.sender), id);
}
}
function validate(address sender, uint id, uint[101][] attributes) returns(bool) {
if (id == 0) {
// Create only
// Require address to be unique
if (id_for_address(sender) != 0) {
ValidationError(ADDRESS, "must be unique");
return false;
}
} else {
// Require sender to be original creator
if (sender != address(db.get_entry(id, ADDRESS, 0))) {
ValidationError(ADDRESS, "must be the owner of this entry");
return false;
}
}
return true;
}
function add_rating(uint id, uint rating) {
if (msg.sender == coordinator.models(REVIEW)) {
uint current_count = db.get_entry(id, RATING_COUNT, 0);
uint current_total = db.get_entry(id, RATING_TOTAL, 0);
db.clear(id, RATING_COUNT);
db.clear(id, RATING_TOTAL);
db.add(id, RATING_COUNT, current_count + 1);
db.add(id, RATING_TOTAL, current_total + rating);
}
}
function id_for_address(address _address) returns(uint) {
return address_index.lookup(uint(_address));
}
function replace(address replacement) {
if (msg.sender == address(coordinator)) {
address_index.transfer_ownership(replacement);
super.replace(replacement);
}
}
}
contract VersionModel is Model {
uint constant DAPP_ID = 0x646170705f6964;
uint constant NUMBER = 0x6e756d626572;
uint constant WHATS_NEW = 0x77686174735f6e6577;
uint constant SCREENSHOTS = 0x73637265656e73686f7473;
uint constant LOCATION = 0x6c6f636174696f6e;
uint constant PRICE = 0x7072696365;
uint constant DAPP = 0x64617070;
uint constant DEVELOPER_ID = 0x646576656c6f7065725f6964;
uint constant DEVELOPER = 0x646576656c6f706572;
uint constant ADDRESS = 0x61646472657373;
uint constant REVIEW = 0x726576696577;
uint constant REVIEWS = 0x72657669657773;
uint constant RATING_COUNT = 0x726174696e675f636f756e74;
uint constant RATING_TOTAL = 0x726174696e675f746f74616c;
uint constant LICENSE = 0x6c6963656e7365;
uint constant LICENSES = 0x6c6963656e736573;
uint constant VERSIONS = 0x76657273696f6e73; // "versions"
uint constant VERSION_ID = 0x76657273696f6e5f6964; // "version_id"
function VersionModel() {
keys.length = 6;
keys[0][0] = DAPP_ID;
keys[1][0] = NUMBER;
keys[2][0] = WHATS_NEW;
keys[3][0] = SCREENSHOTS;
keys[4][0] = LOCATION;
keys[5][0] = PRICE;
keys[0][1] = REFERENCE;
keys[1][1] = STRING;
keys[2][1] = STRING;
keys[3][1] = STRING_ARRAY;
keys[4][1] = STRING;
keys[5][1] = INTEGER;
associations.length = 2;
associations[0][0] = REVIEWS;
associations[0][1] = REVIEW;
associations[1][0] = LICENSES;
associations[1][1] = LICENSE;
read_only.length = 2;
read_only[0][0] = RATING_COUNT;
read_only[1][0] = RATING_TOTAL;
read_only[0][1] = INTEGER;
read_only[1][1] = INTEGER;
}
function create(uint[101][] attributes) returns(uint) {
uint id = super.create(attributes);
if (id != 0) {
uint dapp_id = attributes[0][1];
Model dapp_model = Model(coordinator.models(DAPP));
dapp_model.add_to_association(dapp_id, VERSIONS, id);
dapp_model.set_reference(dapp_id, VERSION_ID, id);
db.add(id, RATING_COUNT, 0);
db.add(id, RATING_TOTAL, 0);
}
return id;
}
function validate(address sender, uint id, uint[101][] attributes) returns(bool) {
// Require dapp ID to be present, and fit into one data slot
if (attributes[0][0] != 1) {
ValidationError(DAPP_ID, "must be present");
return false;
}
// Require dapp ID to be valid
Model dapp_model = Model(coordinator.models(DAPP));
EternalDB dapp_db = EternalDB(dapp_model.db());
uint dapp_id = attributes[0][1];
if (dapp_db.count() < dapp_id) {
ValidationError(DAPP_ID, "is invalid");
return false;
}
// Require sender to be the developer's address
Model developer_model = Model(coordinator.models(DEVELOPER));
EternalDB dev_db = EternalDB(developer_model.db());
uint dev_id = dapp_db.get_entry(dapp_id, DEVELOPER_ID, 0);
address developer = address(dev_db.get_entry(dev_id, ADDRESS, 0));
if (developer != sender) {
ValidationError(DEVELOPER_ID, "is not the right sender");
return false;
}
if (id != 0) {
// Update only
// Require dapp_id to be unchanged
uint current_dapp_id = db.get_entry(id, DAPP_ID, 0);
if (current_dapp_id != dapp_id) {
ValidationError(DAPP_ID, "must not change on update()");
return false;
}
}
return true;
}
function add_rating(uint id, uint rating) {
if (msg.sender == coordinator.models(REVIEW)) {
uint current_count = db.get_entry(id, RATING_COUNT, 0);
uint current_total = db.get_entry(id, RATING_TOTAL, 0);
db.clear(id, RATING_COUNT);
db.clear(id, RATING_TOTAL);
db.add(id, RATING_COUNT, current_count + 1);
db.add(id, RATING_TOTAL, current_total + rating);
}
}
}
contract ReviewModel is Model {
uint constant VERSION_ID = 0x76657273696f6e5f6964;
uint constant RATING = 0x726174696e67;
uint constant DATE = 0x64617465;
uint constant SUBJECT = 0x7375626a656374;
uint constant TEXT = 0x74657874;
uint constant VERSION = 0x76657273696f6e;
uint constant DEVELOPER = 0x646576656c6f706572;
uint constant REVIEWS = 0x72657669657773; // "reviews"
uint constant MODEL_ID = 0x6d6f64656c5f6964; // "model_id"
uint constant MODEL_TYPE = 0x6d6f64656c5f74797065; // "model_type"
function ReviewModel() {
keys.length = 6;
keys[0][0] = MODEL_ID;
keys[1][0] = MODEL_TYPE;
keys[2][0] = RATING;
keys[3][0] = DATE;
keys[4][0] = SUBJECT;
keys[5][0] = TEXT;
keys[0][1] = POLYMORPHIC_REFERENCE;
keys[1][1] = POLYMORPHIC_TYPE;
keys[2][1] = INTEGER;
keys[3][1] = DATETIME;
keys[4][1] = STRING;
keys[5][1] = STRING;
read_only.length = 1;
read_only[0][0] = ADDRESS;
read_only[0][1] = ADDRESS;
}
function create(uint[101][] attributes) returns(uint) {
uint id = super.create(attributes);
if (id != 0) {
// This is a little gross, but it seemed easier than consolidating
// the rating code into a separate contract. TODO: It should be consolidated.
if (attributes[1][1] == DEVELOPER) {
DeveloperModel developer_model = DeveloperModel(coordinator.models(attributes[1][1]));
uint developer_id = attributes[0][1];
developer_model.add_to_association(developer_id, REVIEWS, id);
developer_model.add_rating(developer_id, attributes[2][1]);
}
if (attributes[1][1] == VERSION) {
VersionModel version_model = VersionModel(coordinator.models(attributes[1][1]));
uint version_id = attributes[0][1];
version_model.add_to_association(version_id, REVIEWS, id);
version_model.add_rating(version_id, attributes[2][1]);
}
db.add(id, ADDRESS, uint(msg.sender));
}
return id;
}
function validate(address sender, uint id, uint[101][] attributes) returns(bool) {
// Require model ID to be present, and fit into one data slot
if (attributes[0][0] != 1) {
ValidationError(MODEL_ID, "must be present");
return false;
}
if (attributes[1][0] != 1) {
ValidationError(MODEL_TYPE, "must be present");
return false;
}
if (attributes[1][1] != DEVELOPER && attributes[1][1] != VERSION) {
ValidationError(MODEL_TYPE, "invalid");
return false;
}
// Require model id to be valid
Model model = Model(coordinator.models(attributes[1][1]));
EternalDB model_db = EternalDB(model.db());
uint model_id = attributes[0][1];
if (model_db.count() < model_id) {
ValidationError(MODEL_ID, "invalid");
return false;
}
// Require rating to be present, and fit into one data slot
if (attributes[2][0] != 1) {
ValidationError(RATING, "must be present");
return false;
}
// Require rating to be between 1 and 5
if (attributes[2][1] < 1 || attributes[2][1] > 5) {
ValidationError(RATING, "must be between 1 and 5");
return false;
}
return true;
}
}External Links
Related contracts
EternalDB
Same deployerThe storage contract of the ConsenSys dapp store prototype: a list of records, each a mapping from integer keys to lists of integer values, writable only by its admin.
0x31f8e5...8ef04fNovember 6, 2015ModelCoordinator
Same deployerThe registry of the ConsenSys dapp store prototype that grants model contracts write access to the shared EternalDB.
0x288dcd...a8523eNovember 7, 2015LicenseModel
Same deployerThe licence model of the ConsenSys dapp store prototype: records which user holds a licence for which dapp.
0x09e79b...496375November 7, 2015DualIndex
Same deployerA two-key uint lookup table writable only by its admin, used by the ConsenSys dapp store prototype for licences.
0xb549cd...ac249eNovember 7, 2015AttributeModel
Same deployerThe attribute model of the ConsenSys dapp store prototype: stores typed attributes attached to dapps and developers.
0x7e75db...0ba8c7November 7, 2015DappModel
Same deployerThe dapp model of the ConsenSys dapp store prototype: creates, validates and updates dapp listings kept in an EternalDB.
0xab4192...c9f9eeNovember 7, 2015