Skip to content

Instantly share code, notes, and snippets.

@zurfyx
Created February 24, 2019 16:20
Show Gist options
  • Save zurfyx/0a51fc77322b2f83cf7115f4f4dfae3c to your computer and use it in GitHub Desktop.
Save zurfyx/0a51fc77322b2f83cf7115f4f4dfae3c to your computer and use it in GitHub Desktop.
InVID Rights Management Smart Contract
pragma solidity ^0.4.24;
import "./Ownable.sol";
/**
* @title Contract to register reuse requests on the InVID Rights Management site.
* @dev This contract stores the various request requests and their parties, as well as each of the
* reuse request steps that were taken during the negotiation process.
* The reuse request terms for each of the steps can be found on IPFS.
*/
contract Invid is Ownable {
struct ReuseRequest {
address journalist;
address contentOwner;
Step[] steps;
uint8 stepsCount;
bool revoked;
string hash; // IPFS hash
}
struct Step {
bytes32 id;
bool signedJournalist;
uint64 signedJournalistAt;
bool signedContentOwner;
uint64 signedContentOwnerAt;
string hash; // IPFS hash
}
mapping (bytes32 => ReuseRequest) public reuseRequests; // <reuse request id> -> ReuseRequest
event NewReuseRequest(bytes32 indexed reuseRequestId);
event NewStep(bytes32 indexed reuseRequestId, uint stepIndex);
event SignedStep(bytes32 indexed reuseRequestId, uint stepIndex);
event RevokedReuseRequest(bytes32 indexed reuseRequestId);
modifier onlyReuseRequestParticipant(bytes32 _reuseRequestId) {
ReuseRequest storage _reuseRequest = reuseRequests[_reuseRequestId];
require(
msg.sender == _reuseRequest.journalist || msg.sender == _reuseRequest.contentOwner,
"Unauthorized (neither journalist nor content owner)"
);
_;
}
modifier onlyJournalist(bytes32 _reuseRequestId) {
ReuseRequest storage _reuseRequest = reuseRequests[_reuseRequestId];
require(msg.sender == _reuseRequest.journalist, "Unauthorized (not journalist)");
_;
}
/*
* Creates a new reuse request, considering the creator as the journalist.
* Additionally, the first step is created and marked as signed on the journalist side.
*/
function createReuseRequest(
address _contentOwner,
bytes32 _reuseRequestId,
string _reuseRequestHash,
bytes32 _firstStepId,
string _firstStepHash
) public {
require(_contentOwner != address(0), "Content Owner address should not be 0");
require(reuseRequests[_reuseRequestId].stepsCount == 0, "There already exists a reuse request with that Id");
reuseRequests[_reuseRequestId].journalist = msg.sender;
reuseRequests[_reuseRequestId].contentOwner = _contentOwner;
reuseRequests[_reuseRequestId].hash = _reuseRequestHash;
emit NewReuseRequest(_reuseRequestId);
createStep(_reuseRequestId, _firstStepId, _firstStepHash);
}
/**
* Creates a new step on the reuse request, if:
* - Participant
* - Both participants haven't agreed on a step (unfinished reuse request)
* The signature on the creator side will already be set.
* Note: Two steps can have the same stepId. Participants will still require the step index
* to sign, so it doesn't matter.
* Note2: Cancelled agreements can't have further steps either.
*/
function createStep(bytes32 _reuseRequestId, bytes32 _stepId, string _hash) public onlyReuseRequestParticipant(_reuseRequestId) {
require(!signedBoth(_reuseRequestId), "Reuse request is complete. No further steps allowed");
ReuseRequest storage _reuseRequest = reuseRequests[_reuseRequestId];
Step memory step = msg.sender == _reuseRequest.journalist
? createStepJournalist(_stepId, _hash)
: createStepContentOwner(_stepId, _hash);
_reuseRequest.stepsCount = uint8(_reuseRequest.steps.push(step));
emit NewStep(_reuseRequestId, _reuseRequest.stepsCount - 1);
}
function createStepJournalist(bytes32 _stepId, string _hash) internal view returns (Step) {
return Step({
id: _stepId,
signedJournalist: true,
signedJournalistAt: uint64(block.timestamp),
signedContentOwner: false,
signedContentOwnerAt: 0,
hash: _hash
});
}
function createStepContentOwner(bytes32 _stepId, string _hash) internal view returns (Step) {
return Step({
id: _stepId,
signedJournalist: false,
signedJournalistAt: 0,
signedContentOwner: true,
signedContentOwnerAt: uint64(block.timestamp),
hash: _hash
});
}
/*
* Signs the latest reuse request step, as long as the hash is valid.
* This function can be called unlimited times, but following times will have no effect.
*/
function signStep(bytes32 _reuseRequestId, string _hash) public onlyReuseRequestParticipant(_reuseRequestId) {
ReuseRequest storage _reuseRequest = reuseRequests[_reuseRequestId];
Step storage _lastStep = _reuseRequest.steps[_reuseRequest.stepsCount - 1];
require(
keccak256(abi.encodePacked(_lastStep.hash)) == keccak256(abi.encodePacked(_hash)),
"Latest step hash doesn't match provided"
);
if (msg.sender == _reuseRequest.journalist) {
signStepJournalist(_lastStep);
} else {
signStepContentOwner(_lastStep);
}
emit SignedStep(_reuseRequestId, _reuseRequest.stepsCount - 1);
}
function signStepJournalist(Step storage step) internal {
step.signedJournalist = true;
step.signedJournalistAt = uint64(block.timestamp);
}
function signStepContentOwner(Step storage step) internal {
step.signedContentOwner = true;
step.signedContentOwnerAt = uint64(block.timestamp);
}
/**
* Whether the last reuse request step has been signed by all parties (journalist & content
* owner).
* Beware: the reuse request may have been revoked. Use isAccepted() instead to find out whether
* the reuse request still prevails.
*/
function signedBoth(bytes32 _reuseRequestId) public view returns(bool) {
ReuseRequest memory _reuseRequest = reuseRequests[_reuseRequestId];
if (_reuseRequest.stepsCount == 0) {
return false;
}
uint _lastIndex = _reuseRequest.stepsCount - 1;
Step memory _lastStep = reuseRequests[_reuseRequestId].steps[_lastIndex];
return _lastStep.signedJournalist && _lastStep.signedContentOwner;
}
/**
* Whether as a journalist, you can make use of the reuse request terms.
* This function takes into consideration the revoked status stored on the reuse request.
*/
function isAccepted(bytes32 _reuseRequestId) public view returns(bool) {
return signedBoth(_reuseRequestId) && !reuseRequests[_reuseRequestId].revoked;
}
function getReuseRequestStep(bytes32 _reuseRequestId, uint stepIndex) public view
returns(bytes32 id, bool signedJournalist, bool signedContentOwner, string hash) {
Step memory step = reuseRequests[_reuseRequestId].steps[stepIndex];
return (
step.id,
step.signedJournalist,
step.signedContentOwner,
step.hash
);
}
/**
* As a journalist, you can revoke previously accepted reuse request.
* This function can be called unlimited times, but following times will have no effect.
*/
function revoke(bytes32 _reuseRequestId) public onlyJournalist(_reuseRequestId) {
require(signedBoth(_reuseRequestId), "A reuse request cannot be revoked until both parties have signed");
ReuseRequest storage _reuseRequest = reuseRequests[_reuseRequestId];
_reuseRequest.revoked = true;
emit RevokedReuseRequest(_reuseRequestId);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment