-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathSimpleDistributor.sol
165 lines (134 loc) · 9.6 KB
/
SimpleDistributor.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
import "./AbstractDistributor.sol";
import "./EnergyToken.sol";
import "./EnergyTokenLib.sol";
import "./IEnergyToken.sol";
/**
* The SimpleDistributor distributes certificates based on all forward kinds except
* for property forwards.
*/
contract SimpleDistributor is AbstractDistributor {
EnergyToken public energyToken;
// Token ID => consumption plant address => bool
mapping(uint256 => mapping(address => bool)) completedDistributions;
// Token ID => generation plant address => bool
mapping(uint256 => mapping(address => bool)) completedSurplusDistributions;
mapping(uint64 => mapping(address => uint256)) numberOfCompletedConsumptionBasedDistributions;
bool testing;
modifier onlyEnergyToken() {
require(msg.sender == address(energyToken), "only the energy token contract may invoke this function");
_;
}
constructor(EnergyToken _energyToken, bool _testing, address _owner)
IdentityContract(_energyToken.marketAuthority(), IdentityContract.BalancePeriodConfiguration(0, 0, 0), _owner) {
energyToken = _energyToken;
testing = _testing;
}
// For the definitions of the interface identifiers, see InterfaceIds.sol.
function supportsInterface(bytes4 interfaceID) override(IdentityContract) external pure returns (bool) {
return
interfaceID == 0x01ffc9a7 ||
interfaceID == 0x6f15538d ||
interfaceID == 0x848a042c ||
interfaceID == 0x1fd50459 ||
interfaceID == 0xad467c35;
}
function distribute(address payable _consumptionPlantAddress, uint256 _tokenId) external onlyConsumptionPlants(_consumptionPlantAddress) {
// Distributor applicability check. Required because this contract holding the necessary certificates to pay the consumption plant
// is not sufficient grouns to assume that this is the correct distributor as soon as several forwards may cause payout of the
// same certificates.
require(energyToken.id2Distributor(_tokenId) == this, "Distributor contract does not belong to this _tokenId");
// Single execution check
require(testing || !completedDistributions[_tokenId][_consumptionPlantAddress], "_consumptionPlantAddress can only call distribute() once.");
completedDistributions[_tokenId][_consumptionPlantAddress] = true;
(IEnergyToken.TokenKind tokenKind, uint64 balancePeriod, address generationPlantAddress) = energyToken.getTokenIdConstituents(_tokenId);
// Time period check
require(testing || balancePeriod < getBalancePeriod(block.timestamp), "balancePeriod has not yet ended.");
uint256 certificateTokenId = energyToken.getTokenId(IEnergyToken.TokenKind.Certificate, balancePeriod, generationPlantAddress, 0);
bytes memory tokenIdEncoded = abi.encode(_tokenId);
// Distribution
if(tokenKind == IEnergyToken.TokenKind.AbsoluteForward) {
uint256 totalForwards = energyToken.totalSupply(_tokenId);
uint256 absoluteForwardsOfConsumer = energyToken.balanceOf(_consumptionPlantAddress, _tokenId);
(, uint256 generatedEnergy, , bool generated, ) = energyToken.energyDocumentations(generationPlantAddress, balancePeriod);
require(generated, "Generation plant has not produced any energy.");
energyToken.safeTransferFrom(address(this), _consumptionPlantAddress, certificateTokenId,
Commons.min(absoluteForwardsOfConsumer, (absoluteForwardsOfConsumer * generatedEnergy) / totalForwards), tokenIdEncoded);
return;
}
if(tokenKind == IEnergyToken.TokenKind.GenerationBasedForward) {
uint256 generationBasedForwardsOfConsumer = energyToken.balanceOf(_consumptionPlantAddress, _tokenId);
(, uint256 generatedEnergy, , bool generated, ) = energyToken.energyDocumentations(generationPlantAddress, balancePeriod);
require(generated, "Generation plant has not produced any energy.");
energyToken.safeTransferFrom(address(this), _consumptionPlantAddress, certificateTokenId,
(generationBasedForwardsOfConsumer * generatedEnergy) / 100E18, tokenIdEncoded);
return;
}
if(tokenKind == IEnergyToken.TokenKind.ConsumptionBasedForward) {
uint256 consumptionBasedForwards = energyToken.balanceOf(_consumptionPlantAddress, _tokenId);
(uint256 generatedEnergy, uint256 consumedEnergy) = distribute_getGeneratedAndConsumedEnergy(generationPlantAddress, _consumptionPlantAddress, balancePeriod);
uint256 totalConsumedEnergy = energyToken.energyConsumedRelevantForGenerationPlant(balancePeriod, generationPlantAddress);
// Block to reduce the stack depth.
uint256 min;
{
uint256 option1 = (consumptionBasedForwards * consumedEnergy) / 100E18;
if(totalConsumedEnergy > 0) {
uint256 option2 = (consumptionBasedForwards * consumedEnergy * generatedEnergy) / (100E18 * totalConsumedEnergy);
min = Commons.min(option1, option2);
} else {
min = option1;
}
}
require(energyToken.numberOfRelevantConsumptionPlantsUnmeasuredForGenerationPlant(balancePeriod, generationPlantAddress) == 0,
"Missing energy energy documentations for at least one consumption plant.");
numberOfCompletedConsumptionBasedDistributions[balancePeriod][generationPlantAddress]++;
energyToken.safeTransferFrom(address(this), _consumptionPlantAddress, certificateTokenId, min, tokenIdEncoded);
return;
}
require(false, "Inapplicable token kind.");
}
function distribute_getGeneratedAndConsumedEnergy(address _generationPlantAddress, address _consumptionPlantAddress, uint64 _balancePeriod) internal view returns (uint256 __generatedEnergy, uint256 __consumedEnergy) {
(, uint256 generatedEnergy, , bool gGen, ) = energyToken.energyDocumentations(_generationPlantAddress, _balancePeriod);
(, uint256 consumedEnergy, , bool gCon, ) = energyToken.energyDocumentations(_consumptionPlantAddress, _balancePeriod);
require(gGen && !gCon, "Either the generation plant has not generated or the consumption plant has not consumed any energy.");
return (generatedEnergy, consumedEnergy);
}
/**
* Must only be called by generation plants. Sends surplus certificates to the calling generation plant.
*
* Surplus certificates due to rounding errors are neglected. For surplus due to unsold forwards, the regular distribute() functions has to be called.
*/
function withdrawSurplusCertificates(uint256 _tokenId) external {
(IEnergyToken.TokenKind tokenKind, uint64 balancePeriod, address generationPlantAddress) = energyToken.getTokenIdConstituents(_tokenId);
// Distributor applicability check. Required because this contract holding the necessary certificates to pay the consumption plant
// is not sufficient grouns to assume that this is the correct distributor as soon as several forwards may cause payout of the
// same certificates.
require(energyToken.id2Distributor(_tokenId) == this, "Distributor contract does not belong to this _tokenId");
// Single execution check
require(testing || !completedSurplusDistributions[_tokenId][generationPlantAddress], "_generationPlantAddress can only call withdrawSurplusCertificates() once.");
completedSurplusDistributions[_tokenId][generationPlantAddress] = true;
// Time period check
require(testing || balancePeriod < getBalancePeriod(block.timestamp), "balancePeriod has not yet ended.");
uint256 certificateTokenId = energyToken.getTokenId(IEnergyToken.TokenKind.Certificate, balancePeriod, generationPlantAddress, 0);
bytes memory tokenIdEncoded = abi.encode(_tokenId);
// Surplus Distribution
if(tokenKind == IEnergyToken.TokenKind.AbsoluteForward) {
uint256 totalForwards = energyToken.totalSupply(_tokenId);
(, uint256 generatedEnergy, , bool generated, ) = energyToken.energyDocumentations(generationPlantAddress, balancePeriod);
require(generated, "Generation plant has not produced any energy.");
if(generatedEnergy > totalForwards) {
energyToken.safeTransferFrom(address(this), generationPlantAddress, certificateTokenId, generatedEnergy - totalForwards, tokenIdEncoded);
}
return;
}
if(tokenKind == IEnergyToken.TokenKind.ConsumptionBasedForward) {
// Only allow transfer of undistributable certificates if all consumption plants have gotten their certificates because otherwise it's not possible to figure out how many certificates are undistributable.
require(energyToken.numberOfRelevantConsumptionPlantsForGenerationPlant(balancePeriod, generationPlantAddress) == numberOfCompletedConsumptionBasedDistributions[balancePeriod][generationPlantAddress], "Transfer of undistributable certificates is only allowed after all consumption plants have received their certificates.");
uint256 distributorCertificatesBalance = energyToken.balanceOf(address(this), certificateTokenId);
energyToken.safeTransferFrom(address(this), generationPlantAddress, certificateTokenId, distributorCertificatesBalance, tokenIdEncoded);
return;
}
require(false, "Inapplicable token kind.");
}
}