Skip to content

Commit cef4971

Browse files
authored
Merge pull request #15 from SecurityTokenStandard/eip-1410
Initial reference implementation of ERC1410
2 parents ad667a5 + aa985b1 commit cef4971

13 files changed

Lines changed: 937 additions & 539 deletions

contracts/ERC1410/ERC1410Basic.sol

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
pragma solidity ^0.4.24;
2+
3+
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
4+
import "../math/KindMath.sol";
5+
6+
contract ERC1410Basic {
7+
8+
using SafeMath for uint256;
9+
10+
// Represents a fungible set of tokens.
11+
struct Partition {
12+
uint256 amount;
13+
bytes32 partition;
14+
}
15+
16+
uint256 _totalSupply;
17+
18+
// Mapping from investor to aggregated balance across all investor token sets
19+
mapping (address => uint256) balances;
20+
21+
// Mapping from investor to their partitions
22+
mapping (address => Partition[]) partitions;
23+
24+
// Mapping from (investor, partition) to index of corresponding partition in partitions
25+
// @dev Stored value is always greater by 1 to avoid the 0 value of every index
26+
mapping (address => mapping (bytes32 => uint256)) partitionToIndex;
27+
28+
event TransferByPartition(
29+
bytes32 indexed _fromPartition,
30+
address _operator,
31+
address indexed _from,
32+
address indexed _to,
33+
uint256 _value,
34+
bytes _data,
35+
bytes _operatorData
36+
);
37+
38+
/**
39+
* @dev Total number of tokens in existence
40+
*/
41+
function totalSupply() external view returns (uint256) {
42+
return _totalSupply;
43+
}
44+
45+
/// @notice Counts the sum of all partitions balances assigned to an owner
46+
/// @param _tokenHolder An address for whom to query the balance
47+
/// @return The number of tokens owned by `_tokenHolder`, possibly zero
48+
function balanceOf(address _tokenHolder) external view returns (uint256) {
49+
return balances[_tokenHolder];
50+
}
51+
52+
/// @notice Counts the balance associated with a specific partition assigned to an tokenHolder
53+
/// @param _partition The partition for which to query the balance
54+
/// @param _tokenHolder An address for whom to query the balance
55+
/// @return The number of tokens owned by `_tokenHolder` with the metadata associated with `_partition`, possibly zero
56+
function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256) {
57+
if (_validPartition(_partition, _tokenHolder))
58+
return partitions[_tokenHolder][partitionToIndex[_tokenHolder][_partition] - 1].amount;
59+
else
60+
return 0;
61+
}
62+
63+
/// @notice Use to get the list of partitions `_tokenHolder` is associated with
64+
/// @param _tokenHolder An address corresponds whom partition list is queried
65+
/// @return List of partitions
66+
function partitionsOf(address _tokenHolder) external view returns (bytes32[]) {
67+
bytes32[] memory partitionsList = new bytes32[](partitions[_tokenHolder].length);
68+
for (uint256 i = 0; i < partitions[_tokenHolder].length; i++) {
69+
partitionsList[i] = partitions[_tokenHolder][i].partition;
70+
}
71+
return partitionsList;
72+
}
73+
74+
/// @notice Transfers the ownership of tokens from a specified partition from one address to another address
75+
/// @param _partition The partition from which to transfer tokens
76+
/// @param _to The address to which to transfer tokens to
77+
/// @param _value The amount of tokens to transfer from `_partition`
78+
/// @param _data Additional data attached to the transfer of tokens
79+
/// @return The partition to which the transferred tokens were allocated for the _to address
80+
function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32) {
81+
// Add a function to verify the `_data` parameter
82+
// TODO: Need to create the bytes division of the `_partition` so it can be easily findout in which receiver's partition
83+
// token will transfered. For current implementation we are assuming that the receiver's partition will be same as sender's
84+
// as well as it also pass the `_validPartition()` check. In this particular case we are also assuming that reciever has the
85+
// some tokens of the same partition as well (To avoid the array index out of bound error).
86+
// Note- There is no operator used for the execution of this call so `_operator` value in
87+
// in event is address(0) same for the `_operatorData`
88+
_transferByPartition(msg.sender, _to, _value, _partition, _data, address(0), "");
89+
}
90+
91+
/// @notice The standard provides an on-chain function to determine whether a transfer will succeed,
92+
/// and return details indicating the reason if the transfer is not valid.
93+
/// @param _from The address from whom the tokens get transferred.
94+
/// @param _to The address to which to transfer tokens to.
95+
/// @param _partition The partition from which to transfer tokens
96+
/// @param _value The amount of tokens to transfer from `_partition`
97+
/// @param _data Additional data attached to the transfer of tokens
98+
/// @return ESC (Ethereum Status Code) following the EIP-1066 standard
99+
/// @return Application specific reason codes with additional details
100+
/// @return The partition to which the transferred tokens were allocated for the _to address
101+
function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32) {
102+
// TODO: Applied the check over the `_data` parameter
103+
if (!_validPartition(_partition, _from))
104+
return (0x50, "Partition not exists", bytes32(""));
105+
else if (partitions[_from][partitionToIndex[_from][_partition]].amount < _value)
106+
return (0x52, "Insufficent balance", bytes32(""));
107+
else if (_to == address(0))
108+
return (0x57, "Invalid receiver", bytes32(""));
109+
else if (!KindMath.checkSub(balances[_from], _value) || !KindMath.checkAdd(balances[_to], _value))
110+
return (0x50, "Overflow", bytes32(""));
111+
112+
// Call function to get the receiver's partition. For current implementation returning the same as sender's
113+
return (0x51, "Success", _partition);
114+
}
115+
116+
function _transferByPartition(address _from, address _to, uint256 _value, bytes32 _partition, bytes _data, address _operator, bytes _operatorData) internal {
117+
require(_validPartition(_partition, _from), "Invalid partition");
118+
require(partitions[_from][partitionToIndex[_from][_partition] - 1].amount >= _value, "Insufficient balance");
119+
require(_to != address(0), "0x address not allowed");
120+
uint256 _fromIndex = partitionToIndex[_from][_partition] - 1;
121+
uint256 _toIndex = partitionToIndex[_to][_partition] - 1;
122+
123+
// Changing the state values
124+
partitions[_from][_fromIndex].amount = partitions[_from][_fromIndex].amount.sub(_value);
125+
balances[_from] = balances[_from].sub(_value);
126+
partitions[_to][_toIndex].amount = partitions[_to][_toIndex].amount.add(_value);
127+
balances[_to] = balances[_to].add(_value);
128+
// Emit transfer event.
129+
emit TransferByPartition(_partition, _operator, _from, _to, _value, _data, _operatorData);
130+
}
131+
132+
function _validPartition(bytes32 _partition, address _holder) internal view returns(bool) {
133+
if (partitions[_holder].length < partitionToIndex[_holder][_partition] || partitionToIndex[_holder][_partition] == 0)
134+
return false;
135+
else
136+
return true;
137+
}
138+
139+
140+
141+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
pragma solidity ^0.4.24;
2+
3+
import "./ERC1410Basic.sol";
4+
5+
contract ERC1410Operator is ERC1410Basic {
6+
7+
// Mapping from (investor, partition, operator) to approved status
8+
mapping (address => mapping (bytes32 => mapping (address => bool))) partitionApprovals;
9+
10+
// Mapping from (investor, operator) to approved status (can be used against any partition)
11+
mapping (address => mapping (address => bool)) approvals;
12+
13+
event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
14+
event RevokedOperator(address indexed operator, address indexed tokenHolder);
15+
16+
event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);
17+
event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);
18+
19+
/// @notice Determines whether `_operator` is an operator for all partitions of `_tokenHolder`
20+
/// @param _operator The operator to check
21+
/// @param _tokenHolder The token holder to check
22+
/// @return Whether the `_operator` is an operator for all partitions of `_tokenHolder`
23+
function isOperator(address _operator, address _tokenHolder) public view returns (bool) {
24+
return approvals[_tokenHolder][_operator];
25+
}
26+
27+
/// @notice Determines whether `_operator` is an operator for a specified partition of `_tokenHolder`
28+
/// @param _partition The partition to check
29+
/// @param _operator The operator to check
30+
/// @param _tokenHolder The token holder to check
31+
/// @return Whether the `_operator` is an operator for a specified partition of `_tokenHolder`
32+
function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) public view returns (bool) {
33+
return partitionApprovals[_tokenHolder][_partition][_operator];
34+
}
35+
36+
///////////////////////
37+
/// Operator Management
38+
///////////////////////
39+
40+
/// @notice Authorises an operator for all partitions of `msg.sender`
41+
/// @param _operator An address which is being authorised
42+
function authorizeOperator(address _operator) external {
43+
approvals[msg.sender][_operator] = true;
44+
emit AuthorizedOperator(_operator, msg.sender);
45+
}
46+
47+
/// @notice Revokes authorisation of an operator previously given for all partitions of `msg.sender`
48+
/// @param _operator An address which is being de-authorised
49+
function revokeOperator(address _operator) external {
50+
approvals[msg.sender][_operator] = false;
51+
emit RevokedOperator(_operator, msg.sender);
52+
}
53+
54+
/// @notice Authorises an operator for a given partition of `msg.sender`
55+
/// @param _partition The partition to which the operator is authorised
56+
/// @param _operator An address which is being authorised
57+
function authorizeOperatorByPartition(bytes32 _partition, address _operator) external {
58+
partitionApprovals[msg.sender][_partition][_operator] = true;
59+
emit AuthorizedOperatorByPartition(_partition, _operator, msg.sender);
60+
}
61+
62+
/// @notice Revokes authorisation of an operator previously given for a specified partition of `msg.sender`
63+
/// @param _partition The partition to which the operator is de-authorised
64+
/// @param _operator An address which is being de-authorised
65+
function revokeOperatorByPartition(bytes32 _partition, address _operator) external {
66+
partitionApprovals[msg.sender][_partition][_operator] = false;
67+
emit RevokedOperatorByPartition(_partition, _operator, msg.sender);
68+
}
69+
70+
/// @notice Transfers the ownership of tokens from a specified partition from one address to another address
71+
/// @param _partition The partition from which to transfer tokens
72+
/// @param _from The address from which to transfer tokens from
73+
/// @param _to The address to which to transfer tokens to
74+
/// @param _value The amount of tokens to transfer from `_partition`
75+
/// @param _data Additional data attached to the transfer of tokens
76+
/// @param _operatorData Additional data attached to the transfer of tokens by the operator
77+
/// @return The partition to which the transferred tokens were allocated for the _to address
78+
function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32) {
79+
// TODO: Add a functionality of verifying the `_operatorData`
80+
// TODO: Add a functionality of verifying the `_data`
81+
require(
82+
isOperator(msg.sender, _from) || isOperatorForPartition(_partition, msg.sender, _from),
83+
"Not authorised"
84+
);
85+
_transferByPartition(_from, _to, _value, _partition, _data, msg.sender, _operatorData);
86+
}
87+
88+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
pragma solidity ^0.4.24;
2+
3+
import "./ERC1410Operator.sol";
4+
import "./IERC1410.sol";
5+
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
6+
7+
contract ERC1410Standard is IERC1410, ERC1410Operator, Ownable {
8+
9+
/// @notice Increases totalSupply and the corresponding amount of the specified owners partition
10+
/// @param _partition The partition to allocate the increase in balance
11+
/// @param _tokenHolder The token holder whose balance should be increased
12+
/// @param _value The amount by which to increase the balance
13+
/// @param _data Additional data attached to the minting of tokens
14+
function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external onlyOwner {
15+
// Add the function to validate the `_data` parameter
16+
_validateParams(_partition, _value);
17+
require(_tokenHolder != address(0), "Invalid token receiver");
18+
uint256 index = partitionToIndex[_tokenHolder][_partition];
19+
if (index == 0) {
20+
partitions[_tokenHolder].push(Partition(_value, _partition));
21+
partitionToIndex[_tokenHolder][_partition] = partitions[_tokenHolder].length;
22+
} else {
23+
partitions[_tokenHolder][index - 1].amount = partitions[_tokenHolder][index - 1].amount.add(_value);
24+
}
25+
_totalSupply = _totalSupply.add(_value);
26+
balances[_tokenHolder] = balances[_tokenHolder].add(_value);
27+
emit IssuedByPartition(_partition, _tokenHolder, _value, _data);
28+
}
29+
30+
/// @notice Decreases totalSupply and the corresponding amount of the specified partition of msg.sender
31+
/// @param _partition The partition to allocate the decrease in balance
32+
/// @param _value The amount by which to decrease the balance
33+
/// @param _data Additional data attached to the burning of tokens
34+
function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external {
35+
// Add the function to validate the `_data` parameter
36+
_redeemByPartition(_partition, msg.sender, address(0), _value, _data, "");
37+
}
38+
39+
/// @notice Decreases totalSupply and the corresponding amount of the specified partition of tokenHolder
40+
/// @dev This function can only be called by the authorised operator.
41+
/// @param _partition The partition to allocate the decrease in balance.
42+
/// @param _tokenHolder The token holder whose balance should be decreased
43+
/// @param _value The amount by which to decrease the balance
44+
/// @param _data Additional data attached to the burning of tokens
45+
/// @param _operatorData Additional data attached to the transfer of tokens by the operator
46+
function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data, bytes _operatorData) external {
47+
// Add the function to validate the `_data` parameter
48+
// TODO: Add a functionality of verifying the `_operatorData`
49+
require(_tokenHolder != address(0), "Invalid from address");
50+
require(
51+
isOperator(msg.sender, _tokenHolder) || isOperatorForPartition(_partition, msg.sender, _tokenHolder),
52+
"Not authorised"
53+
);
54+
_redeemByPartition(_partition, _tokenHolder, msg.sender, _value, _data, _operatorData);
55+
}
56+
57+
function _redeemByPartition(bytes32 _partition, address _from, address _operator, uint256 _value, bytes _data, bytes _operatorData) internal {
58+
// Add the function to validate the `_data` parameter
59+
_validateParams(_partition, _value);
60+
require(_validPartition(_partition, _from), "Invalid partition");
61+
uint256 index = partitionToIndex[_from][_partition] - 1;
62+
require(partitions[_from][index].amount >= _value, "Insufficient value");
63+
if (partitions[_from][index].amount == _value) {
64+
_deletePartitionForHolder(_from, _partition, index);
65+
} else {
66+
partitions[_from][index].amount = partitions[_from][index].amount.sub(_value);
67+
}
68+
balances[_from] = balances[_from].sub(_value);
69+
_totalSupply = _totalSupply.sub(_value);
70+
emit RedeemedByPartition(_partition, _operator, _from, _value, _data, _operatorData);
71+
}
72+
73+
function _deletePartitionForHolder(address _holder, bytes32 _partition, uint256 index) internal {
74+
if (index != partitions[_holder].length -1) {
75+
partitions[_holder][index] = partitions[_holder][partitions[_holder].length -1];
76+
partitionToIndex[_holder][partitions[_holder][index].partition] = index + 1;
77+
}
78+
delete partitionToIndex[_holder][_partition];
79+
partitions[_holder].length--;
80+
}
81+
82+
function _validateParams(bytes32 _partition, uint256 _value) internal pure {
83+
require(_value != uint256(0), "Zero value not allowed");
84+
require(_partition != bytes32(0), "Invalid partition");
85+
}
86+
87+
}

contracts/ERC1410/IERC1410.sol

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
pragma solidity ^0.4.24;
2+
3+
interface IERC1410 {
4+
5+
// Token Information
6+
function balanceOf(address _tokenHolder) external view returns (uint256);
7+
function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256);
8+
function partitionsOf(address _tokenHolder) external view returns (bytes32[]);
9+
function totalSupply() external view returns (uint256);
10+
11+
// Token Transfers
12+
function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32);
13+
function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32);
14+
function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32);
15+
16+
// Operator Information
17+
function isOperator(address _operator, address _tokenHolder) external view returns (bool);
18+
function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool);
19+
20+
// Operator Management
21+
function authorizeOperator(address _operator) external;
22+
function revokeOperator(address _operator) external;
23+
function authorizeOperatorByPartition(bytes32 _partition, address _operator) external;
24+
function revokeOperatorByPartition(bytes32 _partition, address _operator) external;
25+
26+
// Issuance / Redemption
27+
function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external;
28+
function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external;
29+
function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data, bytes _operatorData) external;
30+
31+
// Transfer Events
32+
event TransferByPartition(
33+
bytes32 indexed _fromPartition,
34+
address _operator,
35+
address indexed _from,
36+
address indexed _to,
37+
uint256 _value,
38+
bytes _data,
39+
bytes _operatorData
40+
);
41+
42+
// Operator Events
43+
event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
44+
event RevokedOperator(address indexed operator, address indexed tokenHolder);
45+
event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);
46+
event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);
47+
48+
// Issuance / Redemption Events
49+
event IssuedByPartition(bytes32 indexed partition, address indexed to, uint256 value, bytes data);
50+
event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 value, bytes data, bytes operatorData);
51+
52+
}

contracts/ERC1594/ERC1594.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pragma solidity ^0.4.24;
22

33
import "./IERC1594.sol";
4-
import "./ERC20Token.sol";
4+
import "../ERC20Token.sol";
55
import "../math/KindMath.sol";
66
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
77

0 commit comments

Comments
 (0)