From 0ea6bc93653330109b8a511f43266b7b03be4466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Mon, 6 Jan 2020 08:13:56 +0000 Subject: [PATCH 1/7] Production and cosumption load. --- contracts/examples/energy/EnergyMarket.sol | 70 ++++++++++++++-------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/contracts/examples/energy/EnergyMarket.sol b/contracts/examples/energy/EnergyMarket.sol index ad938e0..8f94294 100644 --- a/contracts/examples/energy/EnergyMarket.sol +++ b/contracts/examples/energy/EnergyMarket.sol @@ -2,6 +2,7 @@ pragma solidity ^0.5.10; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/math/Math.sol"; // import "@hq20/contracts/contracts/access/Whitelist.sol"; import "./../../access/Whitelist.sol"; @@ -17,19 +18,18 @@ import "./../../access/Whitelist.sol"; contract EnergyMarket is ERC20, Whitelist { using SafeMath for uint256; - event EnergyProduced(address producer); - event EnergyConsumed(address consumer); + event EnergyProduced(address producer, uint256 time); + event EnergyConsumed(address consumer, uint256 time); - uint256 public load; + mapping(uint256 => uint256) public consumption; + mapping(uint256 => uint256) public production; uint256 public maxPrice; /** * @dev The constructor initializes the underlying currency token and the * smart meter whitelist. The constructor also mints the requested amount * of the underlying currency token to fund the network load. Also sets the - * maximum energy price, used for calculating prices and for when load is 0 - * for production (supplying the first energy unit) or when load is 1 for - * consumption (taking the last energy unit). + * maximum energy price, used for calculating prices. */ constructor (uint256 _initialSupply, uint256 _maxPrice) public @@ -41,39 +41,59 @@ contract EnergyMarket is ERC20, Whitelist { } /** - * @dev The production price is maxPrice / load + 1 + * @dev The production price for each time slot is maxPrice / max((consumption - production + 1), 1) */ - function getProductionPrice() public view returns(uint256) { - return maxPrice.div(load.add(1)); + function getProductionPrice(uint256 _time) public view returns(uint256) { + if (production[_time] >= consumption[_time]) return maxPrice; + return maxPrice.div( + Math.max( + consumption[_time].sub(production[_time].add(1)), + 1 + ) + ); } /** - * @dev The consumption price is maxPrice / load + * @dev The consumption price for each time slot is maxPrice / max((production - consumption + 1), 1) */ - function getConsumptionPrice() public view returns(uint256) { - if (load > 0) return maxPrice.div(load); - else return maxPrice; + function getConsumptionPrice(uint256 _time) public view returns(uint256) { + if (consumption[_time] >= production[_time]) return maxPrice; + return maxPrice.div( + Math.max( + production[_time].sub(consumption[_time].add(1)), + 1 + ) + ); } /** - * @dev Add one energy unit to the distribution network and be paid the - * production price. Only whitelisted smart meters can call this function. + * @dev Add one energy unit to the distribution network at the specified + * time and be paid the production price. Only whitelisted smart meters can + * call this function. */ - function produce() public { + function produce(uint256 _time) public { require(isMember(msg.sender), "Unknown meter."); - this.transfer(msg.sender, getProductionPrice()); - load = load.add(1); - emit EnergyProduced(msg.sender); + this.transfer( + msg.sender, + getProductionPrice(_time) + ); + production[_time] = production[_time].add(1); + emit EnergyProduced(msg.sender, _time); } /** - * @dev Take one energy unit from the distribution network by paying the - * consumption price. Only whitelisted smart meters can call this function. + * @dev Take one energy unit from the distribution network at the specified + * time by paying the consumption price. Only whitelisted smart meters can + * call this function. */ - function consume() public { + function consume(uint256 _time) public { require(isMember(msg.sender), "Unknown meter."); - this.transferFrom(msg.sender, address(this), getConsumptionPrice()); - load = load.sub(1); - emit EnergyConsumed(msg.sender); + this.transferFrom( + msg.sender, + address(this), + getConsumptionPrice(_time) + ); + consumption[_time] = consumption[_time].add(1); + emit EnergyConsumed(msg.sender, _time); } } \ No newline at end of file From 0e0aa0b8dadbc3f601af53f55b98ca67c4bb2bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Mon, 6 Jan 2020 18:34:14 +0000 Subject: [PATCH 2/7] Fixed tests. --- test/examples/energy/EnergyMarket.test.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/examples/energy/EnergyMarket.test.ts b/test/examples/energy/EnergyMarket.test.ts index 51bc621..93355f6 100644 --- a/test/examples/energy/EnergyMarket.test.ts +++ b/test/examples/energy/EnergyMarket.test.ts @@ -25,6 +25,8 @@ contract('EnergyMarket', (accounts) => { const initialSupply = 1000000; const maxPrice = 10; + const timeSlot = 1; + beforeEach(async () => { energyMarket = await EnergyMarket.new( initialSupply, @@ -37,38 +39,38 @@ contract('EnergyMarket', (accounts) => { * @test {EnergyMarket#produce} */ it('Produce energy', async () => { - expect(energyMarket.produce({ from: authorized })).to.emit('EnergyProduced').withArgs(authorized); + expect(energyMarket.produce(timeSlot, { from: authorized })).to.emit('EnergyProduced').withArgs(authorized); }); /** * @test {EnergyMarket#produce} */ it('Produce throws with unauthorized producer', async () => { - expect(energyMarket.produce({ from: unauthorized })).to.revertWith('Unknown meter.'); + expect(energyMarket.produce(timeSlot, { from: unauthorized })).to.revertWith('Unknown meter.'); }); /** * @test {EnergyMarket#consume} */ it('Consume energy', async () => { - await energyMarket.produce({from: authorized }); + await energyMarket.produce(timeSlot, {from: authorized }); await energyMarket.approve( - energyMarket.address, await energyMarket.getConsumptionPrice(), { from: authorized }, + energyMarket.address, await energyMarket.getConsumptionPrice(1), { from: authorized }, ); - expect(energyMarket.consume({ from: authorized })).to.emit('EnergyConsumed').withArgs(authorized); + expect(energyMarket.consume(timeSlot, { from: authorized })).to.emit('EnergyConsumed').withArgs(authorized); }); /** * @test {EnergyMarket#consume} */ it('Consume throws with unauthorized consumer', async () => { - expect(energyMarket.consume({ from: unauthorized })).to.revertWith('Unknown meter.'); + expect(energyMarket.consume(timeSlot, { from: unauthorized })).to.revertWith('Unknown meter.'); }); /** * @test {EnergyMarket#consume} */ it('Consume throws if consumer has insufficient balance', async () => { - expect(energyMarket.consume({ from: authorized })).to.revert; + expect(energyMarket.consume(timeSlot, { from: authorized })).to.revert; }); }); From bbc9f8f65f9b1b64537b15217595120a057c920f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Mon, 6 Jan 2020 18:37:54 +0000 Subject: [PATCH 3/7] Moved to drafts. --- contracts/{ => drafts}/examples/energy/EnergyMarket.sol | 2 +- test/{ => drafts}/examples/energy/EnergyMarket.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename contracts/{ => drafts}/examples/energy/EnergyMarket.sol (98%) rename test/{ => drafts}/examples/energy/EnergyMarket.test.ts (94%) diff --git a/contracts/examples/energy/EnergyMarket.sol b/contracts/drafts/examples/energy/EnergyMarket.sol similarity index 98% rename from contracts/examples/energy/EnergyMarket.sol rename to contracts/drafts/examples/energy/EnergyMarket.sol index 8f94294..823fa06 100644 --- a/contracts/examples/energy/EnergyMarket.sol +++ b/contracts/drafts/examples/energy/EnergyMarket.sol @@ -4,7 +4,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/math/Math.sol"; // import "@hq20/contracts/contracts/access/Whitelist.sol"; -import "./../../access/Whitelist.sol"; +import "./../../../access/Whitelist.sol"; /** diff --git a/test/examples/energy/EnergyMarket.test.ts b/test/drafts/examples/energy/EnergyMarket.test.ts similarity index 94% rename from test/examples/energy/EnergyMarket.test.ts rename to test/drafts/examples/energy/EnergyMarket.test.ts index 93355f6..5087675 100644 --- a/test/examples/energy/EnergyMarket.test.ts +++ b/test/drafts/examples/energy/EnergyMarket.test.ts @@ -1,9 +1,9 @@ import { BigNumber } from 'bignumber.js'; // tslint:disable-next-line:no-var-requires -import { EnergyMarketInstance } from '../../../types/truffle-contracts'; +import { EnergyMarketInstance } from '../../../../types/truffle-contracts'; const EnergyMarket = artifacts.require( - './examples/energy/EnergyMarket.sol', + './drafts/examples/energy/EnergyMarket.sol', ) as Truffle.Contract; // tslint:disable:no-var-requires From 3c8509ecca2eecd36f05f012bfe37050e68de970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Mon, 6 Jan 2020 18:53:01 +0000 Subject: [PATCH 4/7] Simplified price formula. --- .../drafts/examples/energy/EnergyMarket.sol | 26 +++++-------------- .../examples/energy/EnergyMarket.test.ts | 4 +-- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/contracts/drafts/examples/energy/EnergyMarket.sol b/contracts/drafts/examples/energy/EnergyMarket.sol index 823fa06..c9413cd 100644 --- a/contracts/drafts/examples/energy/EnergyMarket.sol +++ b/contracts/drafts/examples/energy/EnergyMarket.sol @@ -23,7 +23,7 @@ contract EnergyMarket is ERC20, Whitelist { mapping(uint256 => uint256) public consumption; mapping(uint256 => uint256) public production; - uint256 public maxPrice; + uint256 public basePrice; /** * @dev The constructor initializes the underlying currency token and the @@ -31,39 +31,27 @@ contract EnergyMarket is ERC20, Whitelist { * of the underlying currency token to fund the network load. Also sets the * maximum energy price, used for calculating prices. */ - constructor (uint256 _initialSupply, uint256 _maxPrice) + constructor (uint256 _initialSupply, uint256 _basePrice) public ERC20() Whitelist() { _mint(address(this), _initialSupply); - maxPrice = _maxPrice; + basePrice = _basePrice; } /** - * @dev The production price for each time slot is maxPrice / max((consumption - production + 1), 1) + * @dev The production price for each time slot */ function getProductionPrice(uint256 _time) public view returns(uint256) { - if (production[_time] >= consumption[_time]) return maxPrice; - return maxPrice.div( - Math.max( - consumption[_time].sub(production[_time].add(1)), - 1 - ) - ); + return basePrice * production[_time] / (consumption[_time] + 1); } /** - * @dev The consumption price for each time slot is maxPrice / max((production - consumption + 1), 1) + * @dev The consumption price for each time slot */ function getConsumptionPrice(uint256 _time) public view returns(uint256) { - if (consumption[_time] >= production[_time]) return maxPrice; - return maxPrice.div( - Math.max( - production[_time].sub(consumption[_time].add(1)), - 1 - ) - ); + return basePrice * consumption[_time] / (production[_time] + 1); } /** diff --git a/test/drafts/examples/energy/EnergyMarket.test.ts b/test/drafts/examples/energy/EnergyMarket.test.ts index 5087675..1f6dbd4 100644 --- a/test/drafts/examples/energy/EnergyMarket.test.ts +++ b/test/drafts/examples/energy/EnergyMarket.test.ts @@ -23,14 +23,14 @@ contract('EnergyMarket', (accounts) => { let energyMarket: EnergyMarketInstance; const initialSupply = 1000000; - const maxPrice = 10; + const basePrice = 10; const timeSlot = 1; beforeEach(async () => { energyMarket = await EnergyMarket.new( initialSupply, - maxPrice, + basePrice, ); await energyMarket.addMember(authorized); }); From 54ec7bfe6a2fae0a69c25150446f1964fe7b8fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Tue, 7 Jan 2020 14:55:43 +0000 Subject: [PATCH 5/7] Tweaked price formula. --- contracts/drafts/examples/energy/EnergyMarket.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contracts/drafts/examples/energy/EnergyMarket.sol b/contracts/drafts/examples/energy/EnergyMarket.sol index c9413cd..3b988f2 100644 --- a/contracts/drafts/examples/energy/EnergyMarket.sol +++ b/contracts/drafts/examples/energy/EnergyMarket.sol @@ -44,14 +44,20 @@ contract EnergyMarket is ERC20, Whitelist { * @dev The production price for each time slot */ function getProductionPrice(uint256 _time) public view returns(uint256) { - return basePrice * production[_time] / (consumption[_time] + 1); + return basePrice * Math.max( + production[_time].sub(consumption[_time]), + 1 + ); } /** * @dev The consumption price for each time slot */ function getConsumptionPrice(uint256 _time) public view returns(uint256) { - return basePrice * consumption[_time] / (production[_time] + 1); + return basePrice * Math.max( + consumption[_time].sub(production[_time]), + 1 + ); } /** From 3bdc6732218cdf348d4a7d333544bf58bac30a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Tue, 7 Jan 2020 16:45:23 +0000 Subject: [PATCH 6/7] Tweaked price formulas again. --- .../drafts/examples/energy/EnergyMarket.sol | 55 +++++++++++++------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/contracts/drafts/examples/energy/EnergyMarket.sol b/contracts/drafts/examples/energy/EnergyMarket.sol index 3b988f2..41306b4 100644 --- a/contracts/drafts/examples/energy/EnergyMarket.sol +++ b/contracts/drafts/examples/energy/EnergyMarket.sol @@ -1,8 +1,8 @@ pragma solidity ^0.5.10; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "@openzeppelin/contracts/math/Math.sol"; +// import "@openzeppelin/contracts/math/SafeMath.sol"; +// import "@openzeppelin/contracts/math/Math.sol"; // import "@hq20/contracts/contracts/access/Whitelist.sol"; import "./../../../access/Whitelist.sol"; @@ -16,14 +16,17 @@ import "./../../../access/Whitelist.sol"; * meters that communicate the production and consumption of energy. */ contract EnergyMarket is ERC20, Whitelist { - using SafeMath for uint256; + // using SafeMath for uint256; event EnergyProduced(address producer, uint256 time); event EnergyConsumed(address consumer, uint256 time); - mapping(uint256 => uint256) public consumption; - mapping(uint256 => uint256) public production; - uint256 public basePrice; + // uint128 is used here to facilitate the price formula + // Casting between uint128 and int256 never overflows + // int256(uint128) - int256(uint128) never overflows + mapping(uint256 => uint128) public consumption; + mapping(uint256 => uint128) public production; + uint128 public basePrice; /** * @dev The constructor initializes the underlying currency token and the @@ -31,7 +34,7 @@ contract EnergyMarket is ERC20, Whitelist { * of the underlying currency token to fund the network load. Also sets the * maximum energy price, used for calculating prices. */ - constructor (uint256 _initialSupply, uint256 _basePrice) + constructor (uint256 _initialSupply, uint128 _basePrice) public ERC20() Whitelist() @@ -41,12 +44,15 @@ contract EnergyMarket is ERC20, Whitelist { } /** - * @dev The production price for each time slot + * @dev The production price for each time slot. */ function getProductionPrice(uint256 _time) public view returns(uint256) { - return basePrice * Math.max( - production[_time].sub(consumption[_time]), - 1 + return uint256( + max( + 0, + int256(basePrice) * + (3 + safeSub(production[_time], consumption[_time])) + ) ); } @@ -54,9 +60,12 @@ contract EnergyMarket is ERC20, Whitelist { * @dev The consumption price for each time slot */ function getConsumptionPrice(uint256 _time) public view returns(uint256) { - return basePrice * Math.max( - consumption[_time].sub(production[_time]), - 1 + return uint256( + max( + 0, + int256(basePrice) * + (3 + safeSub(consumption[_time], production[_time])) + ) ); } @@ -71,7 +80,7 @@ contract EnergyMarket is ERC20, Whitelist { msg.sender, getProductionPrice(_time) ); - production[_time] = production[_time].add(1); + production[_time] = production[_time] + 1; emit EnergyProduced(msg.sender, _time); } @@ -87,7 +96,21 @@ contract EnergyMarket is ERC20, Whitelist { address(this), getConsumptionPrice(_time) ); - consumption[_time] = consumption[_time].add(1); + consumption[_time] = consumption[_time] + 1; emit EnergyConsumed(msg.sender, _time); } + + /** + * @dev Returns the largest of two numbers. + */ + function max(int256 a, int256 b) internal pure returns (int256) { + return a >= b ? a : b; + } + + /** + * @dev Substracts b from a using types safely casting from uint to int. + */ + function safeSub(uint128 a, uint128 b) internal pure returns (int256) { + return int256(a) - int256(b); + } } \ No newline at end of file From 1368822de2407909b59307b870ac0bf2e38a93d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Tue, 7 Jan 2020 16:47:29 +0000 Subject: [PATCH 7/7] Cleaned comments. --- contracts/drafts/examples/energy/EnergyMarket.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/drafts/examples/energy/EnergyMarket.sol b/contracts/drafts/examples/energy/EnergyMarket.sol index 41306b4..829f2f0 100644 --- a/contracts/drafts/examples/energy/EnergyMarket.sol +++ b/contracts/drafts/examples/energy/EnergyMarket.sol @@ -1,8 +1,6 @@ pragma solidity ^0.5.10; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -// import "@openzeppelin/contracts/math/SafeMath.sol"; -// import "@openzeppelin/contracts/math/Math.sol"; // import "@hq20/contracts/contracts/access/Whitelist.sol"; import "./../../../access/Whitelist.sol"; @@ -16,7 +14,6 @@ import "./../../../access/Whitelist.sol"; * meters that communicate the production and consumption of energy. */ contract EnergyMarket is ERC20, Whitelist { - // using SafeMath for uint256; event EnergyProduced(address producer, uint256 time); event EnergyConsumed(address consumer, uint256 time); @@ -108,7 +105,7 @@ contract EnergyMarket is ERC20, Whitelist { } /** - * @dev Substracts b from a using types safely casting from uint to int. + * @dev Substracts b from a using types safely casting from uint128 to int256. */ function safeSub(uint128 a, uint128 b) internal pure returns (int256) { return int256(a) - int256(b);