Created
February 24, 2019 16:20
-
-
Save zurfyx/0a51fc77322b2f83cf7115f4f4dfae3c to your computer and use it in GitHub Desktop.
InVID Rights Management Smart Contract
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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