Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

Commit

Permalink
add Attestor
Browse files Browse the repository at this point in the history
  • Loading branch information
Christophe Le Bars committed Jul 27, 2018
1 parent 336aee3 commit af0d191
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 29 deletions.
2 changes: 1 addition & 1 deletion contracts/RougeFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import "./RougeRegistry.sol";

contract RougeFactory is RougeRegistry {

string public version = '0.9';
bytes8 public version = '0.10';

// The Rouge Token contract address
RGETokenInterface public rge;
Expand Down
68 changes: 49 additions & 19 deletions contracts/SimpleRougeCampaign.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import "./RougeFactoryInterface.sol";

contract SimpleRougeCampaign {

string public version = '0.9';
bytes8 public version = '0.10';

// The Rouge Token contract address
RGETokenInterface public rge;
Expand All @@ -34,24 +34,37 @@ contract SimpleRougeCampaign {
uint32 public redeemed = 0;

constructor(address _issuer, uint32 _issuance, address _rge, uint256 _tare, address _factory) public {

require(_issuance > 0);

issuer = _issuer;
issuance = _issuance;
rge = RGETokenInterface(_rge);
tare = _tare;
factory = RougeFactoryInterface(_factory);
}

// web3.eth.sign compat prefix XXX mv to lib
function prefixed(bytes32 _message) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _message));
}
enum Authorization { Acquisition, Redemption }

function getInfo() public view returns (bytes) {
return abi.encodePacked(issuer, scheme, campaignExpiration, name);
mapping (address => mapping (uint => bool)) public canAuthorize;

event AttestorAddition(address indexed attestor, Authorization auth);

function addAttestor(address _attestor, Authorization _auth) onlyBy(issuer) public {
canAuthorize[_attestor][uint(_auth)] = true;
emit AttestorAddition(_attestor, _auth);
}

event AttestorRemoval(address indexed attestor, Authorization auth);

function getState() public view returns (bytes) {
return abi.encodePacked(issuance, available, acquired, redeemed);
function removeAttestor(address _attestor, Authorization _auth) onlyBy(issuer) public {
canAuthorize[_attestor][uint(_auth)] = false;
emit AttestorRemoval(_attestor, _auth);
}

// web3.eth.sign compat prefix XXX mv to lib
function prefixed(bytes32 _message) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _message));
}

bytes4 public scheme;
Expand All @@ -78,9 +91,22 @@ contract SimpleRougeCampaign {
scheme = _scheme;

emit Issuance(_scheme, _name, _campaignExpiration);

}

function issueWithAttestor(bytes4 _scheme, string _name, uint _campaignExpiration, address _attestor) onlyBy(issuer) public {
issue(_scheme, _name, _campaignExpiration);
addAttestor(_attestor, Authorization.Acquisition);
addAttestor(_attestor, Authorization.Redemption);
}

function getInfo() public view returns (bytes) {
return abi.encodePacked(issuer, scheme, campaignExpiration, name);
}

function getState() public view returns (bytes) {
return abi.encodePacked(issuance, campaignIssued, available, acquired, redeemed);
}

modifier CampaignOpen() {
require(campaignIssued);
require(now < campaignExpiration);
Expand Down Expand Up @@ -111,13 +137,12 @@ contract SimpleRougeCampaign {
}
}

event Log(bytes32 hash);

// _hash is any hashed msg that confirm issuer authorisation for the note acquisition
function acquire(bytes32 _hash, uint8 v, bytes32 r, bytes32 s) CampaignOpen public returns (bool success) {
// _hash is any hashed msg that confirm attestor(often issuer) authorization for the note acquisition
function acquire(bytes32 _hash, uint8 v, bytes32 r, bytes32 s, address _attestor) CampaignOpen public returns (bool success) {
require(msg.sender != issuer);
require(_hash == keccak256(abi.encodePacked('acceptAcquisition', this, msg.sender)));
require(ecrecover(prefixed(_hash), v, r, s) == issuer);
require(canAuthorize[_attestor][uint(Authorization.Acquisition)]);
require(ecrecover(prefixed(_hash), v, r, s) == _attestor);
return acquisition(msg.sender);
}

Expand Down Expand Up @@ -155,11 +180,12 @@ contract SimpleRougeCampaign {
return true;
}

// _hash is any hashed msg agreed between issuer and bearer
// WARNING: replay protection not implemented at protocol level
function redeem(bytes32 _hash, uint8 v, bytes32 r, bytes32 s) CampaignOpen public returns (bool success) {
// _hash is any hashed msg that confirm attestor(often issuer) authorization for the note redemption
function redeem(bytes32 _hash, uint8 v, bytes32 r, bytes32 s, address _attestor) CampaignOpen public returns (bool success) {
require(msg.sender != issuer);
require(_hash == keccak256(abi.encodePacked('acceptRedemption', this, msg.sender)));
require(ecrecover(prefixed(_hash), v, r, s) == issuer);
require(canAuthorize[_attestor][uint(Authorization.Redemption)]);
require(ecrecover(prefixed(_hash), v, r, s) == _attestor);
return redemption(msg.sender);
}

Expand All @@ -170,6 +196,10 @@ contract SimpleRougeCampaign {
return redemption(_bearer);
}

function getWorkflow(address _bearer) public view returns (bytes) {
return abi.encodePacked(hasNote(_bearer), hasRedeemed(_bearer));
}

function kill() onlyBy(issuer) public {

// burn the tare for unredeemed notes if campaign has started
Expand Down
5 changes: 4 additions & 1 deletion test/RougeFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ contract('RougeFactory', function(accounts) {
assert.equal(campaign_version, factory_version, "factory and campaign contract version are the same");

const campaign_state = await campaign.getState.call();
assert.equal(campaign_state, '0x0000000a000000000000000000000000', "return null state");
assert.equal(campaign_state, '0x0000000a00000000000000000000000000', "encoded state is correct");

const issuer_balance_after = await rge.balanceOf.call(issuer);
assert.equal(issuer_balance_after.toNumber(), tokens - deposit, "issuer has sent tokens as a deposit to the factory");
Expand All @@ -79,6 +79,9 @@ contract('RougeFactory', function(accounts) {
assert.equal(campaign_balance.toNumber(), deposit, "the tokens deposit is now in the new campaign contract");

});

// todo create with zero notes


});

62 changes: 54 additions & 8 deletions test/SimpleRougeCampaign.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ contract('SimpleRougeCampaign', function(accounts) {

const rge = await RGEToken.deployed();

const issuer = accounts[2];
const issuer = accounts[3];
const issuance = 10;
const deposit = 50 * 10**6;

const campaign = await new_campaign(rge, issuer, issuance, deposit);

// expiration of the campaign in 2 days
const expiration = Math.trunc((new Date()).getTime() / 1000) + 60*60*24*2
await campaign.issue('0x02010000', 'no acquisition/noredemtion campaign', expiration, {gas: 2000000, from: issuer});
await campaign.issue('0x02010000', 'no acquisition/noredemtion campaign', expiration, {from: issuer});

const available = await campaign.available.call();
assert.equal(available.toNumber(), issuance, "check notes available after issuance");
Expand Down Expand Up @@ -88,7 +88,7 @@ contract('SimpleRougeCampaign', function(accounts) {
const campaign = await new_campaign(rge, issuer, issuance, deposit);

const expiration = Math.trunc((new Date()).getTime() / 1000) + 60*60*24*2
await campaign.issue('0x02010000', 'issuer test', expiration, {from: issuer});
await campaign.issueWithAttestor('0x02010000', 'issuer test', expiration, issuer, {from: issuer});

await campaign.distributeNote(bearer, {from: issuer});

Expand Down Expand Up @@ -127,26 +127,72 @@ contract('SimpleRougeCampaign', function(accounts) {
const campaign = await new_campaign(rge, issuer, issuance, deposit);

const expiration = Math.trunc((new Date()).getTime() / 1000) + 60*60*24*2
await campaign.issue('0x02010000', 'acceptRedemption Test', expiration, {from: issuer});
await campaign.issueWithAttestor('0x02010000', 'acceptRedemption Test', expiration, issuer, {from: issuer});

// call acquire with auth message and issuer signature
const auth1 = create_auth_hash('acceptAcquisition', campaign.address, bearer)
const sign1 = get_signature(issuer, auth1)
await campaign.acquire(auth1, sign1.v, sign1.r, sign1.s, {from: bearer});
await campaign.acquire(auth1, sign1.v, sign1.r, sign1.s, issuer, {from: bearer});

const acquired = await campaign.acquired.call();
assert.equal(acquired.toNumber(), 1, "check notes acquired after distributeNote");

// call acceptRedemption with auth message and bearer signature
// call acceptRedemption with auth message and issuer signature
const auth2 = create_auth_hash('acceptRedemption', campaign.address, bearer)
const sign2 = get_signature(issuer, auth2)
await campaign.redeem(auth2, sign2.v, sign2.r, sign2.s, {from: bearer});
await campaign.redeem(auth2, sign2.v, sign2.r, sign2.s, issuer, {from: bearer});

const redeemed = await campaign.redeemed.call();
assert.equal(redeemed.toNumber(), 1, "note(s) redeemed after confirmRedemption");

const campaign_state = await campaign.getState.call();
assert.equal(campaign_state, '0x0000000a01000000090000000100000001', "return null state");

await campaign.kill({from: issuer});

const burned = tare * (issuance - redeemed);

const campaign_balance_after = await rge.balanceOf.call(campaign.address);
assert.equal(campaign_balance_after.toNumber(), 0, "the campaign has no more rge after kill");

const issuer_balance_after = await rge.balanceOf.call(issuer);
assert.equal(issuer_balance_after.toNumber(), tokens - burned, "the issuer has his tokens back less tare for unredeemed notes");

});

it("one acquisition/one redemption by bearer using attestor", async function() {

const rge = await RGEToken.deployed();

const issuer = accounts[2];
const attestor = accounts[3];
const bearer = accounts[4];
const issuance = 10;
const deposit = 50 * 10**6;

const campaign = await new_campaign(rge, issuer, issuance, deposit);

const expiration = Math.trunc((new Date()).getTime() / 1000) + 60*60*24*2
await campaign.issueWithAttestor('0x02010000', 'acceptRedemption Test', expiration, attestor, {from: issuer});

// call acquire with auth message and attestor signature
const auth1 = create_auth_hash('acceptAcquisition', campaign.address, bearer)
const sign1 = get_signature(attestor, auth1)
await campaign.acquire(auth1, sign1.v, sign1.r, sign1.s, attestor, {from: bearer});

const acquired = await campaign.acquired.call();
assert.equal(acquired.toNumber(), 1, "check notes acquired after distributeNote");

// call acceptRedemption with auth message and attestor signature
const auth2 = create_auth_hash('acceptRedemption', campaign.address, bearer)
const sign2 = get_signature(attestor, auth2)
await campaign.redeem(auth2, sign2.v, sign2.r, sign2.s, attestor, {from: bearer});

const redeemed = await campaign.redeemed.call();
assert.equal(redeemed.toNumber(), 1, "note(s) redeemed after confirmRedemption");

const campaign_state = await campaign.getState.call();
assert.equal(campaign_state, '0x0000000a000000090000000100000001', "return null state");
assert.equal(campaign_state, '0x0000000a01000000090000000100000001', "return null state");

await campaign.kill({from: issuer});

Expand Down

0 comments on commit af0d191

Please sign in to comment.