-
Notifications
You must be signed in to change notification settings - Fork 0
/
ERC1410Basic.sol
246 lines (215 loc) · 8.62 KB
/
ERC1410Basic.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "./openzeppelin/SafeMath.sol";
import "./openzeppelin/KindMath.sol";
import "./ERC1410Snapshot.sol";
abstract contract ERC1410Basic is ERC1410Snapshot {
using SafeMath for uint256;
// Represents a fungible set of tokens.
struct Partition {
uint256 amount;
bytes32 partition;
}
uint256 _totalSupply;
// Publicly viewable list of all unique partitions
bytes32[] public partitionList;
// Mapping from investor to aggregated balance across all investor token sets
mapping(address => uint256) balances;
// Mapping from investor to their partitions
mapping(address => Partition[]) partitions;
// Mapping from partition to total supply
mapping(bytes32 => uint256) partitionTotalSupply;
// Mapping from (investor, partition) to index of corresponding partition in partitions
// @dev Stored value is always greater by 1 to avoid the 0 value of every index
mapping(address => mapping(bytes32 => uint256)) partitionToIndex;
event TransferByPartition(
bytes32 indexed _fromPartition,
address indexed _from,
address indexed _to,
uint256 _value
);
/**
* @dev Total number of tokens in existence
*/
function totalSupply() external view returns (uint256) {
return _totalSupply;
}
/**
* @dev Total number of tokens in existence of a given partition
* @param _partition The partition for which to query the total supply
* @return Total supply of a given partition
*/
function _totalSupplyByPartition(
bytes32 _partition
) internal view returns (uint256) {
return partitionTotalSupply[_partition];
}
function _increaseTotalSupplyByPartition(
bytes32 _partition,
uint256 _amount
) internal {
partitionTotalSupply[_partition] = partitionTotalSupply[_partition].add(
_amount
);
}
function _decreaseTotalSupplyByPartition(
bytes32 _partition,
uint256 _amount
) internal {
partitionTotalSupply[_partition] = partitionTotalSupply[_partition].sub(
_amount
);
}
/// @notice Counts the sum of all partitions balances assigned to an owner
/// @param _tokenHolder An address for whom to query the balance
/// @return The number of tokens owned by `_tokenHolder`, possibly zero
function _balanceOf(address _tokenHolder) internal view returns (uint256) {
return balances[_tokenHolder];
}
function balanceOf(address _tokenHolder) external view returns (uint256) {
return _balanceOf(_tokenHolder);
}
/// @notice Counts the balance associated with a specific partition assigned to an tokenHolder
/// @param _partition The partition for which to query the balance
/// @param _tokenHolder An address for whom to query the balance
/// @return The number of tokens owned by `_tokenHolder` with the metadata associated with `_partition`, possibly zero
function _balanceOfByPartition(
bytes32 _partition,
address _tokenHolder
) internal view returns (uint256) {
if (_validPartition(_partition, _tokenHolder))
return
partitions[_tokenHolder][
partitionToIndex[_tokenHolder][_partition] - 1
].amount;
else return 0;
}
function balanceOfByPartition(
bytes32 _partition,
address _tokenHolder
) external view returns (uint256) {
return _balanceOfByPartition(_partition, _tokenHolder);
}
/// @notice Use to get the list of partitions `_tokenHolder` is associated with
/// @param _tokenHolder An address corresponds whom partition list is queried
/// @return List of partitions
function partitionsOf(
address _tokenHolder
) external view returns (bytes32[] memory) {
bytes32[] memory partitionsList = new bytes32[](
partitions[_tokenHolder].length
);
for (uint256 i = 0; i < partitions[_tokenHolder].length; i++) {
partitionsList[i] = partitions[_tokenHolder][i].partition;
}
return partitionsList;
}
/// @notice The standard provides an on-chain function to determine whether a transfer will succeed,
/// and return details indicating the reason if the transfer is not valid.
/// @param _from The address from whom the tokens get transferred.
/// @param _to The address to which to transfer tokens to.
/// @param _partition The partition from which to transfer tokens
/// @param _value The amount of tokens to transfer from `_partition`
/// @return ESC (Ethereum Status Code) following the EIP-1066 standard
/// @return Application specific reason codes with additional details
/// @return The partition to which the transferred tokens were allocated for the _to address
function canTransferByPartition(
address _from,
address _to,
bytes32 _partition,
uint256 _value
) external view returns (bytes1, bytes32, bytes32) {
// TODO: Applied the check over the `_data` parameter
if (!_validPartition(_partition, _from))
return (0x50, "Partition not exists", bytes32(""));
else if (
partitions[_from][partitionToIndex[_from][_partition]].amount <
_value
) return (0x52, "Insufficent balance", bytes32(""));
else if (_to == address(0))
return (0x57, "Invalid receiver", bytes32(""));
else if (
!KindMath.checkSub(balances[_from], _value) ||
!KindMath.checkAdd(balances[_to], _value)
) return (0x50, "Overflow", bytes32(""));
// Call function to get the receiver's partition. For current implementation returning the same as sender's
return (0x51, "Success", _partition);
}
function _transferByPartition(
address _from,
address _to,
uint256 _value,
bytes32 _partition
) internal {
require(_validPartition(_partition, _from), "Invalid partition");
require(
partitions[_from][partitionToIndex[_from][_partition] - 1].amount >=
_value,
"Insufficient balance"
);
require(_to != address(0), "0x address not allowed");
uint256 _fromIndex = partitionToIndex[_from][_partition] - 1;
if (!validPartitionForReceiver(_partition, _to)) {
partitions[_to].push(Partition(0, _partition));
partitionToIndex[_to][_partition] = partitions[_to].length;
// Add new partition to the partitionList if it does not already exist
// @note Partitions list should not get too long otherwise it will be impractical to use
bool partitionExists = false;
for (uint256 i = 0; i < partitionList.length; i++) {
if (partitionList[i] == _partition) {
partitionExists = true;
break;
}
}
if (!partitionExists) {
partitionList.push(_partition);
}
}
uint256 _toIndex = partitionToIndex[_to][_partition] - 1;
// Changing the state values
partitions[_from][_fromIndex].amount = partitions[_from][_fromIndex]
.amount
.sub(_value);
balances[_from] = balances[_from].sub(_value);
partitions[_to][_toIndex].amount = partitions[_to][_toIndex].amount.add(
_value
);
balances[_to] = balances[_to].add(_value);
// take snapshot of the state after transfer
_takeSnapshot(
_partition,
_balanceOfByPartition(_partition, _from),
_from
);
_takeSnapshot(_partition, _balanceOfByPartition(_partition, _to), _to);
_takeSnapshot(
_partition,
_totalSupplyByPartition(_partition),
address(0)
);
// Emit transfer event.
emit TransferByPartition(_partition, _from, _to, _value);
}
function _validPartition(
bytes32 _partition,
address _holder
) internal view returns (bool) {
if (
partitions[_holder].length <
partitionToIndex[_holder][_partition] ||
partitionToIndex[_holder][_partition] == 0
) return false;
else return true;
}
function validPartitionForReceiver(
bytes32 _partition,
address _to
) public view returns (bool) {
for (uint256 i = 0; i < partitions[_to].length; i++) {
if (partitions[_to][i].partition == _partition) {
return true;
}
}
return false;
}
}