Minimal Proxy Contract for 0x057509d22e54dbfeb991a00ad6136eaf0373f488.
EIP-1167 - minimal bytecode implementation that delegates all calls to a known address
EIP-1167 - minimal bytecode implementation that delegates all calls to a known address
- Contract name:
- Publication
- Optimization enabled
- false
- Compiler version
- v0.8.12+commit.f00d7308
- Verified at
- 2025-02-21T18:28:21.599709Z
Publication.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;
import "./Editor.sol";
import "./Identity.sol";
import "./HitchensUnorderedAddressSet.sol";
import "./OwnableUpgradeable.sol";
// Decentral Publication Contracts v1.0 (contracts/Publication.sol)
/** @title Contract for one specific publication which is owned by the publication's author */
contract Publication is OwnableUpgradeable {
using HitchensUnorderedAddressSetLib for HitchensUnorderedAddressSetLib.Set;
// Different roles an endorser can have
enum EndorserRole{ NONE, READER, SUPERVISOR, EXAMINER }
// Different states a publication can have
enum PublicationState{ NONE, SUBMITTED, PUBLISHED, RETRACTED }
struct Endorsement {
bool exists;
string document;
EndorserRole role;
}
// Version of the publication contract
uint8 public constant version = 1;
// List of endorsers of the publication
mapping(address => Endorsement) public endorsers;
HitchensUnorderedAddressSetLib.Set internal endorsersSet;
// Identifier of the publishing journal
uint8 public journal;
// File hashes for the published document and it's meta data
string public document;
string public metadata;
// State of the publication process
PublicationState public state;
// Address of the editor proxy contract
Editor public editorProxy;
// Address of the identity proxy contract
Identity public identityProxy;
event LogNewPublication(address sender, string document, string metadata, PublicationState state);
event LogNewEndorsement(address sender, string document, string comment);
event LogUpdateEndorserRole(address sender, address endorser, string document, EndorserRole oldRole, EndorserRole newRole);
event LogUpdateEndorsementDocument(address sender, address endorser, string oldDocument, string newDocument);
event LogUpdateEndorsementComment(address sender, address endorser, string document, string comment);
event LogEndorsementRevocation(address sender, address endorser, string document, string comment);
event LogUpdatePublicationDocument(address sender, string oldDocument, string newDocument, string comment);
event LogUpdatePublicationMetadata(address sender, string oldMetadata, string newMetadata, string comment);
event LogPublicationPublished(address sender, string document, string metadata);
event LogPublicationRetraction(address sender, string comment);
/**
* Called by the author to initialize the contract variables
* @param _owner Address of the author of the publication
* @param _document Off-chain reference to the main document of the publication (e.g., IPFS hash)
* @param _metadata Off-chain reference to the publication metadata document (e.g., IPFS hash)
* @param _identity Off-chain reference to the identity document (e.g., IPFS hash)
* @param _editorProxy Address to the Editor (proxy) contract with which the clones are initialized
* @param _identityProxy Address to the Identity (proxy) contract which maps addresses to real world identities
* @param _journal Identifier of the publishing journal
*/
function initialize(address _owner, string memory _document, string memory _metadata, string memory _identity, Editor _editorProxy, Identity _identityProxy, uint8 _journal) external initializer {
require(
bytes(_document).length > 0 &&
bytes(_metadata).length > 0 &&
bytes(_identity).length > 0,
"Input for document, identity, or metadata cannot be empty"
);
identityProxy = _identityProxy;
try identityProxy.hasIdentity(tx.origin) returns (bool result) {
if(!result) {
try identityProxy.addIdentity(_identity) {
} catch Error(string memory reason){
require(false, reason);
}
}
} catch Error(string memory reason){
require(false, reason);
}
editorProxy = _editorProxy;
journal = _journal;
document = _document;
metadata = _metadata;
state = PublicationState.SUBMITTED;
emit LogNewPublication(tx.origin, _document, _metadata, state);
__Ownable_init();
transferOwnership(_owner);
}
/**
* Endorse the current publication document hash. Function automatically creates a new mapping
* in the Identity contract for the endorser if no mapping exists.
* @param _identity Off-chain reference to the identity document (e.g., IPFS hash)
* @param _comment Comment of the endorser (e.g., reason for his endorsement)
*/
function endorse(string memory _identity, string calldata _comment) notRetracted external {
require(msg.sender != owner(), "Function cannot be called by the contract owner");
require(!endorsersSet.exists(msg.sender), "Function cannot be called by an endorser");
try identityProxy.hasIdentity(msg.sender) returns (bool result) {
if(!result) {
try identityProxy.addIdentity(_identity) {
} catch {
require(false, "Identity could not be stored in Identity contract");
}
}
} catch {
require(false, "Identity could not be stored in Identity contract");
}
endorsersSet.insert(msg.sender);
endorsers[msg.sender] = Endorsement(true,document,EndorserRole.READER);
emit LogNewEndorsement(msg.sender, document, _comment);
}
/**
* Updates the current role assigned to the specified endorser
* @param _endorser Address of the endorser whose role is to be updated
* @param _role New role to be assigned
*/
function updateEndorserRole(address _endorser, EndorserRole _role) onlyOwnerOrEditor(keccak256(abi.encodePacked(_endorser,_role))) endorsementExists(_endorser) external {
require(_role != EndorserRole.NONE, "Invalid endorser role");
Endorsement memory e = endorsers[_endorser];
EndorserRole oldRole = e.role;
e.role = _role;
endorsers[_endorser] = e;
emit LogUpdateEndorserRole(msg.sender, _endorser, e.document, oldRole, _role);
}
/**
* Updates the endorsement of the caller to the current document hash version
*/
function updateEndorsementDocument() endorsementExists(msg.sender) external {
string memory oldDocument = endorsers[msg.sender].document;
endorsers[msg.sender].document = document;
emit LogUpdateEndorsementDocument(msg.sender, msg.sender, oldDocument, document);
}
/**
* Updates the endorsement comment of the caller to the provided text
* @param _comment New comment of the endorser (e.g., reason for his endorsement)
*/
function updateEndorsementComment(string calldata _comment) endorsementExists(msg.sender) external {
emit LogUpdateEndorsementComment(msg.sender, msg.sender, endorsers[msg.sender].document, _comment);
}
/**
* Updates the endorsement comment of the specified endorser to the provided text
* @param _endorser Address of the endorser whose comment is to be updated
* @param _comment New comment of the endorser (e.g., reason for his endorsement)
*/
function updateEndorsementCommentExt(address _endorser, string calldata _comment) onlyEditor(keccak256(abi.encodePacked(_endorser,_comment))) endorsementExists(_endorser) external {
emit LogUpdateEndorsementComment(msg.sender, _endorser, endorsers[_endorser].document, _comment);
}
/**
* Removes the endorsement of the caller from the publication
* @param _comment Reason for retracting the endorsement
*/
function revokeEndorsement(string calldata _comment) endorsementExists(msg.sender) external {
__deleteEndorsement(msg.sender, _comment);
}
/**
* Removes the endorsement of the specified endorser from the publication
* @param _endorser Address of the endorser whose endorsement is to be revoked
* @param _comment Reason for revoking the endorsement
*/
function revokeEndorsementExt(address _endorser, string calldata _comment) onlyOwnerOrEditor(keccak256(abi.encodePacked(_endorser,_comment))) endorsementExists(_endorser) external {
__deleteEndorsement(_endorser, _comment);
}
/**
* Publishes the publication if the contract state includes the required document hashes
* and at least one privileged edorsement (i.e., from an endorser with supervisor or examiner role)
*/
function publish() onlyEditor(keccak256("")) external {
require(state == PublicationState.SUBMITTED, "Function requires contract state: submitted");
require(
bytes(document).length > 0 &&
bytes(metadata).length > 0,
"Input for document or metadata cannot be empty"
);
uint counter = 0;
for(uint i = 0; i < endorsersSet.count(); i++) {
Endorsement memory e = endorsers[endorsersSet.keyAtIndex(i)];
if((e.role == EndorserRole.SUPERVISOR || e.role == EndorserRole.EXAMINER) && keccak256(bytes(e.document)) == keccak256(bytes(document))) {
counter++;
}
}
require(counter >= 1,"Insufficient number of privileged endorsements");
state = PublicationState.PUBLISHED;
emit LogPublicationPublished(msg.sender, document, metadata);
}
/**
* Update the document hash
* @param _document Off-chain reference to the new main document of the publication (e.g., IPFS hash)
* @param _comment Comment describing the document changes
*/
function updateDocument(string memory _document, string calldata _comment) onlyOwnerOrEditor(keccak256(abi.encodePacked(_document,_comment))) notRetracted external {
require(
bytes(_document).length > 0,
"Input for document cannot be empty"
);
string memory oldDocument = document;
document = _document;
emit LogUpdatePublicationDocument(msg.sender, oldDocument, document, _comment);
}
/**
* Update the metadata hash
* @param _metadata Off-chain reference to the new publication metadata document (e.g., IPFS hash)
* @param _comment Comment describing the document changes
*/
function updateMetadata(string memory _metadata, string calldata _comment) onlyOwnerOrEditor(keccak256(abi.encodePacked(_metadata,_comment))) notRetracted external {
require(
bytes(_metadata).length > 0,
"Input for metadata cannot be empty"
);
string memory oldMetadata = metadata;
metadata = _metadata;
emit LogUpdatePublicationMetadata(msg.sender, oldMetadata, metadata, _comment);
}
/**
* Ireversibly retracts the publication
* @param _comment Comment describing the reason for retracting the publication
*/
function retractPublication(string calldata _comment) onlyOwnerOrEditor(keccak256(abi.encodePacked(_comment))) notRetracted external {
state = PublicationState.RETRACTED;
delete document;
delete metadata;
while (endorsersSet.count() > 0) {
address key = endorsersSet.keyAtIndex(0);
delete endorsers[key];
endorsersSet.remove(key);
}
delete endorsersSet;
emit LogPublicationRetraction(msg.sender, _comment);
}
/**
* Removes the endorsement of the specified endorser from the publication
* @param _endorser Address of the endorser whose endorsement is to be removed
* @param _comment Reason for removing the endorsement
*/
function __deleteEndorsement(address _endorser, string calldata _comment) internal {
endorsersSet.remove(_endorser);
string memory doc = endorsers[_endorser].document;
delete endorsers[_endorser];
emit LogEndorsementRevocation(msg.sender, _endorser, doc, _comment);
}
/**
* Asserts whether the caller is allowed to perform a certain action
* @param _data Encoded description of the action
*/
function assertEditorialPermission(bytes32 _data) internal view returns (bool) {
bytes32 action = keccak256(abi.encodePacked(msg.sig,_data));
try editorProxy.actionAllowed(action) returns (bool allowed) {
return allowed;
} catch {
return false;
}
}
/**
* Only editors may perfom the asserted action
* @param _data Encoded description of the action
*/
modifier onlyEditor(bytes32 _data){
require(assertEditorialPermission(_data), "Function can only be called with editorial access rights");
_;
}
/**
* Only the publication author or an editor may perform the asserted action
* @param _data Encoded description of the action
*/
modifier onlyOwnerOrEditor(bytes32 _data){
require(msg.sender == owner() || assertEditorialPermission(_data), "Function is restricted to the contract owner and editors");
_;
}
/**
* Allows to only call a function if the given endorsement exists
* @param _endorser Address of the endorser to be admitted
*/
modifier endorsementExists(address _endorser) {
require(endorsersSet.exists(_endorser), "No matching endorsement found");
_;
}
/**
* Allows to only call a function only if the publication has not been retracted earlier
*/
modifier notRetracted(){
require(state != PublicationState.RETRACTED, "Function cannot be called on a retracted publication");
_;
}
}
Contract ABI
[{"type":"event","name":"LogEndorsementRevocation","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"address","name":"endorser","internalType":"address","indexed":false},{"type":"string","name":"document","internalType":"string","indexed":false},{"type":"string","name":"comment","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"LogNewEndorsement","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"string","name":"document","internalType":"string","indexed":false},{"type":"string","name":"comment","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"LogNewPublication","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"string","name":"document","internalType":"string","indexed":false},{"type":"string","name":"metadata","internalType":"string","indexed":false},{"type":"uint8","name":"state","internalType":"enum Publication.PublicationState","indexed":false}],"anonymous":false},{"type":"event","name":"LogPublicationPublished","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"string","name":"document","internalType":"string","indexed":false},{"type":"string","name":"metadata","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"LogPublicationRetraction","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"string","name":"comment","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"LogUpdateEndorsementComment","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"address","name":"endorser","internalType":"address","indexed":false},{"type":"string","name":"document","internalType":"string","indexed":false},{"type":"string","name":"comment","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"LogUpdateEndorsementDocument","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"address","name":"endorser","internalType":"address","indexed":false},{"type":"string","name":"oldDocument","internalType":"string","indexed":false},{"type":"string","name":"newDocument","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"LogUpdateEndorserRole","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"address","name":"endorser","internalType":"address","indexed":false},{"type":"string","name":"document","internalType":"string","indexed":false},{"type":"uint8","name":"oldRole","internalType":"enum Publication.EndorserRole","indexed":false},{"type":"uint8","name":"newRole","internalType":"enum Publication.EndorserRole","indexed":false}],"anonymous":false},{"type":"event","name":"LogUpdatePublicationDocument","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"string","name":"oldDocument","internalType":"string","indexed":false},{"type":"string","name":"newDocument","internalType":"string","indexed":false},{"type":"string","name":"comment","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"LogUpdatePublicationMetadata","inputs":[{"type":"address","name":"sender","internalType":"address","indexed":false},{"type":"string","name":"oldMetadata","internalType":"string","indexed":false},{"type":"string","name":"newMetadata","internalType":"string","indexed":false},{"type":"string","name":"comment","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"type":"address","name":"previousOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"document","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract Editor"}],"name":"editorProxy","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"endorse","inputs":[{"type":"string","name":"_identity","internalType":"string"},{"type":"string","name":"_comment","internalType":"string"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"exists","internalType":"bool"},{"type":"string","name":"document","internalType":"string"},{"type":"uint8","name":"role","internalType":"enum Publication.EndorserRole"}],"name":"endorsers","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract Identity"}],"name":"identityProxy","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"initialize","inputs":[{"type":"address","name":"_owner","internalType":"address"},{"type":"string","name":"_document","internalType":"string"},{"type":"string","name":"_metadata","internalType":"string"},{"type":"string","name":"_identity","internalType":"string"},{"type":"address","name":"_editorProxy","internalType":"contract Editor"},{"type":"address","name":"_identityProxy","internalType":"contract Identity"},{"type":"uint8","name":"_journal","internalType":"uint8"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"journal","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"metadata","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"owner","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"publish","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"renounceOwnership","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"retractPublication","inputs":[{"type":"string","name":"_comment","internalType":"string"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"revokeEndorsement","inputs":[{"type":"string","name":"_comment","internalType":"string"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"revokeEndorsementExt","inputs":[{"type":"address","name":"_endorser","internalType":"address"},{"type":"string","name":"_comment","internalType":"string"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"enum Publication.PublicationState"}],"name":"state","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferOwnership","inputs":[{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateDocument","inputs":[{"type":"string","name":"_document","internalType":"string"},{"type":"string","name":"_comment","internalType":"string"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateEndorsementComment","inputs":[{"type":"string","name":"_comment","internalType":"string"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateEndorsementCommentExt","inputs":[{"type":"address","name":"_endorser","internalType":"address"},{"type":"string","name":"_comment","internalType":"string"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateEndorsementDocument","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateEndorserRole","inputs":[{"type":"address","name":"_endorser","internalType":"address"},{"type":"uint8","name":"_role","internalType":"enum Publication.EndorserRole"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateMetadata","inputs":[{"type":"string","name":"_metadata","internalType":"string"},{"type":"string","name":"_comment","internalType":"string"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"version","inputs":[]}]
Deployed ByteCode
0x363d3d373d3d3d363d73057509d22e54dbfeb991a00ad6136eaf0373f4885af43d82803e903d91602b57fd5bf3