diff --git a/contracts/EIP20.sol b/contracts/EIP20.sol index 3cfb808..f8a7eb6 100644 --- a/contracts/EIP20.sol +++ b/contracts/EIP20.sol @@ -22,7 +22,7 @@ contract EIP20 is EIP20Interface { uint8 public decimals; //How many decimals to show. string public symbol; //An identifier: eg SBX - function EIP20( + constructor ( uint256 _initialAmount, string _tokenName, uint8 _decimalUnits, diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol index 7e7fe8d..23c76fb 100644 --- a/contracts/Migrations.sol +++ b/contracts/Migrations.sol @@ -8,15 +8,15 @@ contract Migrations { if (msg.sender == owner) _; } - function Migrations() { + constructor() public { owner = msg.sender; } - function setCompleted(uint completed) restricted { + function setCompleted(uint completed) restricted public { last_completed_migration = completed; } - function upgrade(address new_address) restricted { + function upgrade(address new_address) restricted public { Migrations upgraded = Migrations(new_address); upgraded.setCompleted(last_completed_migration); } diff --git a/contracts/SimpleRougeCampaign.sol b/contracts/SimpleRougeCampaign.sol index 93b54a0..50afca7 100644 --- a/contracts/SimpleRougeCampaign.sol +++ b/contracts/SimpleRougeCampaign.sol @@ -41,6 +41,11 @@ contract SimpleRougeCampaign { factory = RougeFactoryInterface(_factory); } + // web3.eth.sign compat prefix XXX mv to lib + function prefixed(bytes32 hash) internal pure returns (bytes32) { + return keccak256("\x19Ethereum Signed Message:\n32", hash); + } + string public name; bool public campaignIssued; uint public campaignExpiration; @@ -51,7 +56,9 @@ contract SimpleRougeCampaign { uint256 rgeBalance = rge.balanceOf(this); require(rgeBalance >= issuance * tare); - // TODO XXX limit expiration to now + 3 months ? + // minimum campaign duration 1 day, maximum 120 days + require(_campaignExpiration >= now + 60*60*24); + require(_campaignExpiration <= now + 60*60*24*120); name = _name; campaignIssued = true; @@ -66,9 +73,6 @@ contract SimpleRougeCampaign { _; } - /* ********** ********** ********** */ - /* the acquisition Register (track if an address has a note) XXX to replace by Int ? */ - mapping (address => bool) acquisitionRegister; function hasNote(address _bearer) constant public returns (bool yes) { @@ -135,25 +139,18 @@ contract SimpleRougeCampaign { return true; } - // web3.eth.sign compat prefix - function prefixed(bytes32 hash) internal pure returns (bytes32) { - return keccak256("\x19Ethereum Signed Message:\n32", hash); - } - // _hash is any hashed msg agreed between issuer and bearer // WARNING: replay protection not implemented at protocol level function acceptRedemption(address _bearer, bytes32 _hash, uint8 v, bytes32 r, bytes32 s) CampaignOpen onlyBy(issuer) public returns (bool success) { - bytes32 message = prefixed(_hash); - require(ecrecover(message, v, r, s) == _bearer); - return redeemNote(_bearer); } - function confirmRedemption(bytes32 _hashmsg, uint8 v, bytes32 r, bytes32 s) CampaignOpen public returns (bool success) { - require(ecrecover(_hashmsg, v, r, s) == issuer); + function confirmRedemption(bytes32 _hash, uint8 v, bytes32 r, bytes32 s) CampaignOpen public returns (bool success) { + bytes32 message = prefixed(_hash); + require(ecrecover(message, v, r, s) == issuer); return redeemNote(msg.sender); } diff --git a/contracts/TestRGEToken.sol b/contracts/TestRGEToken.sol index ac8ab48..9fc495b 100644 --- a/contracts/TestRGEToken.sol +++ b/contracts/TestRGEToken.sol @@ -19,11 +19,14 @@ contract TestRGEToken is EIP20 { /* RGEToken */ address owner; - string public version = 'v0.01'; + string public version = 'v0.03'; uint256 public totalSupply = 1000000000 * 10**uint(decimals); uint256 public reserveY1 = 300000000 * 10**uint(decimals); uint256 public reserveY2 = 200000000 * 10**uint(decimals); + /* set a maximum per address */ + uint256 public maxBalance = 1000000 * 10**uint(decimals); + modifier onlyBy(address _address) { require(msg.sender == _address); _; @@ -35,6 +38,7 @@ contract TestRGEToken is EIP20 { } function giveMeRGE(uint256 _value) public returns (bool success) { + require(balances[msg.sender] + _value <= maxBalance); require(balances[owner] >= _value); balances[owner] -= _value; balances[msg.sender] += _value; diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index 8cb8f51..ef395da 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -1,6 +1,6 @@ -var TestRGEToken = artifacts.require("./TestRGEToken.sol"); -var RougeFactory = artifacts.require("./RougeFactory.sol"); +const TestRGEToken = artifacts.require("./TestRGEToken.sol"); +const RougeFactory = artifacts.require("./RougeFactory.sol"); module.exports = async function(deployer) { diff --git a/test/RougeFactory.js b/test/RougeFactory.js index 2b10857..d59794c 100644 --- a/test/RougeFactory.js +++ b/test/RougeFactory.js @@ -1,145 +1,58 @@ -var RGEToken = artifacts.require("./TestRGEToken.sol"); -var Factory = artifacts.require("./RougeFactory.sol"); -var SimpleRougeCampaign = artifacts.require("./SimpleRougeCampaign.sol"); +const RGEToken = artifacts.require("./TestRGEToken.sol"); +const Factory = artifacts.require("./RougeFactory.sol"); +const SimpleRougeCampaign = artifacts.require("./SimpleRougeCampaign.sol"); -var tare = 0.1 * 10**6; /* price price is 0.1 rge in beta phase */ +const tare = 0.1 * 10**6; /* tare price is 0.1 rge in beta phase */ contract('RougeFactory', function(accounts) { + it("factory has correct parameters", async function() { + + const rge = await RGEToken.deployed(); + const factory = await Factory.deployed(); + + const ftare = await factory.tare.call(); + assert.equal(ftare.toNumber(), tare, "tare price is set correctly in factory"); + + }); + it("create a simple Rouge campaign", async function() { - var issuer = accounts[1]; - var tokens = 1000 * 10**6; /* 1K RGE tokens */ + const issuer = accounts[1]; + const tokens = 1000 * 10**6; /* 1K RGE tokens */ - var issuance = 10; - var deposit = 50 * 10**6; + const issuance = 10; + const deposit = 50 * 10**6; - let rge = await RGEToken.deployed(); - let factory = await Factory.deployed(); + const rge = await RGEToken.deployed(); + const factory = await Factory.deployed(); - let issuer_balance_before = await rge.balanceOf.call(issuer); + const issuer_balance_before = await rge.balanceOf.call(issuer); assert.equal(issuer_balance_before.toNumber(), 0, "issuer has no rge tokens to start with"); await rge.giveMeRGE(tokens, {from: issuer}); - let issuer_balance_post = await rge.balanceOf.call(issuer); + const issuer_balance_post = await rge.balanceOf.call(issuer); assert.equal(issuer_balance_post.toNumber(), tokens, "issuer has receive tokens to create a campaign"); await rge.newCampaign(issuance, deposit, {from: issuer, gas: 2000000, gasPrice: web3.toWei(1, "gwei")}) - let issuer_balance_after = await rge.balanceOf.call(issuer); + 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"); - let campaign_count = await factory.get_all_count.call(); + const campaign_count = await factory.get_all_count.call(); assert.equal(campaign_count.toNumber(), 1, "one campaign has been created"); - let campaign_address = await factory.get_campaign.call(issuer, 0); + const campaign_address = await factory.get_campaign.call(issuer, 0); - let factory_balance = await rge.balanceOf.call(factory.address); + const factory_balance = await rge.balanceOf.call(factory.address); assert.equal(factory_balance.toNumber(), 0, "no tokens deposit in the factory"); - let campaign_balance = await rge.balanceOf.call(campaign_address); + const campaign_balance = await rge.balanceOf.call(campaign_address); assert.equal(campaign_balance.toNumber(), deposit, "the tokens deposit is now in the new campaign contract"); }); - it("simple tare burning test with no redemption", async function() { - - var issuer = accounts[2]; - var tokens = 1000 * 10**6; - - var issuance = 10; - var deposit = 50 * 10**6; - - let rge = await RGEToken.deployed(); - let factory = await Factory.deployed(); - - await rge.giveMeRGE(tokens, {from: issuer}); - await rge.newCampaign(issuance, deposit, {from: issuer, gas: 2000000, gasPrice: web3.toWei(1, "gwei")}) - let campaign_address = await factory.get_campaign.call(issuer, 0); - - let ftare = await factory.tare.call(); - assert.equal(ftare.toNumber(), tare, "tare price is set correctly in factory"); - - let campaign_balance = await rge.balanceOf.call(campaign_address); - assert.equal(campaign_balance.toNumber(), deposit, "the tokens deposit is now in the new campaign contract"); - - let campaign = SimpleRougeCampaign.at(campaign_address); - - // very long expiration // 19 January, 2038 03:14:07 UT ( 2147483647 ) - await campaign.issue('Test Simple 2', 2147483647, {from: issuer}); - - let available = await campaign.available.call(); - assert.equal(available.toNumber(), issuance, "check notes available after issuance"); - - await campaign.kill({from: issuer}); - - let burned = tare * issuance; - - let campaign_balance_after = await rge.balanceOf.call(campaign_address); - assert.equal(campaign_balance_after.toNumber(), 0, "the campaign has no more rge after kill"); - - let 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 10 notes"); - - }); - - it("acceptRedemption from issuer", async function() { - - var issuer = accounts[3]; - var bearer = accounts[4]; - - var tokens = 1000 * 10**6; - var issuance = 10; - var deposit = 50 * 10**6; - - let rge = await RGEToken.deployed(); - let factory = await Factory.deployed(); - - await rge.giveMeRGE(tokens, {from: issuer}); - await rge.newCampaign(issuance, deposit, {from: issuer, gas: 2000000, gasPrice: web3.toWei(1, "gwei")}) - let campaign_address = await factory.get_campaign.call(issuer, 0); - - let campaign_balance = await rge.balanceOf.call(campaign_address); - assert.equal(campaign_balance.toNumber(), deposit, "the tokens deposit is now in the new campaign contract"); - - let campaign = SimpleRougeCampaign.at(campaign_address); - - await campaign.issue('acceptRedemption Test', 2147483647, {from: issuer}); - await campaign.giveNote(bearer, {from: issuer}); - - let acquired = await campaign.acquired.call(); - assert.equal(acquired.toNumber(), 1, "check notes acquired after giveNote"); - - // at minimum, msg needs to include the campaign address to protect against replay - let msg = campaign_address + 'valid ticket'; - - // bearer signature that to be used by issuer - let signature = web3.eth.sign(bearer, web3.sha3(msg)); - signature = signature.substr(2); - const r = '0x' + signature.slice(0, 64) - const s = '0x' + signature.slice(64, 128) - const v = '0x' + signature.slice(128, 130) - const v_decimal = web3.toDecimal(v) + 27 - - await campaign.acceptRedemption(bearer, web3.sha3(msg), v_decimal, r, s, {from: issuer}); - - let redeemed = await campaign.redeemed.call(); - assert.equal(redeemed.toNumber(), 1, "notes redeemed after confirmRedemption"); - - await campaign.kill({from: issuer}); - - let burned = tare * (issuance - 1); - - let campaign_balance_after = await rge.balanceOf.call(campaign_address); - assert.equal(campaign_balance_after.toNumber(), 0, "the campaign has no more rge after kill"); - - let 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"); - - }); - - }); diff --git a/test/SimpleRougeCampaign.js b/test/SimpleRougeCampaign.js new file mode 100644 index 0000000..bdfc75b --- /dev/null +++ b/test/SimpleRougeCampaign.js @@ -0,0 +1,107 @@ + +const RGEToken = artifacts.require("./TestRGEToken.sol"); +const Factory = artifacts.require("./RougeFactory.sol"); +const SimpleRougeCampaign = artifacts.require("./SimpleRougeCampaign.sol"); + +const tare = 0.1 * 10**6; /* tare price is 0.1 rge in beta phase */ + +const new_campaign = async function(rge, issuer, tokens, issuance, deposit) { + + const factory = await Factory.deployed(); + + await rge.giveMeRGE(tokens, {from: issuer}); + await rge.newCampaign(issuance, deposit, {from: issuer, gas: 2000000, gasPrice: web3.toWei(1, "gwei")}) + const campaign_address = await factory.get_campaign.call(issuer, 0); + + return SimpleRougeCampaign.at(campaign_address); + +} + +const get_signature = function(account, message) { + + const signature = web3.eth.sign(account, web3.sha3(message)).substr(2) + return { + r: '0x' + signature.slice(0, 64), + s: '0x' + signature.slice(64, 128), + v: web3.toDecimal( '0x' + signature.slice(128, 130) ) + 27 + } + +} + +contract('SimpleRougeCampaign', function(accounts) { + + it("simple tare burning test with no redemption", async function() { + + const rge = await RGEToken.deployed(); + + const issuer = accounts[2]; + const tokens = 1000 * 10**6; + const issuance = 10; + const deposit = 50 * 10**6; + + const campaign = await new_campaign(rge, issuer, tokens, issuance, deposit); + + // expiration of the campaign in 2 days + const expiration = Math.trunc((new Date()).getTime() / 1000) + 60*60*24*2 + await campaign.issue('tare burning', expiration, {from: issuer}); + + const available = await campaign.available.call(); + assert.equal(available.toNumber(), issuance, "check notes available after issuance"); + + await campaign.kill({from: issuer}); + + const burned = tare * issuance; + + 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 10 notes"); + + }); + + it("acceptRedemption from issuer", async function() { + + const rge = await RGEToken.deployed(); + + const issuer = accounts[3]; + const bearer = accounts[4]; + const tokens = 1000 * 10**6; + const issuance = 10; + const deposit = 50 * 10**6; + + const campaign = await new_campaign(rge, issuer, tokens, issuance, deposit); + + const expiration = Math.trunc((new Date()).getTime() / 1000) + 60*60*24*2 + await campaign.issue('acceptRedemption Test', expiration, {from: issuer}); + + await campaign.giveNote(bearer, {from: issuer}); + + const acquired = await campaign.acquired.call(); + assert.equal(acquired.toNumber(), 1, "check notes acquired after giveNote"); + + // bearer signature that to be used by issuer + // at minimum, msg needs to include the campaign address to protect against replay + const msg = 'this is a valid note of ' + campaign.address + const sign = get_signature(bearer, msg) + + await campaign.acceptRedemption(bearer, web3.sha3(msg), sign.v, sign.r, sign.s, {from: issuer}); + + const redeemed = await campaign.redeemed.call(); + assert.equal(redeemed.toNumber(), 1, "notes redeemed after confirmRedemption"); + + await campaign.kill({from: issuer}); + + const burned = tare * (issuance - 1); + + 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"); + + }); + + +}); +