From c3102e01d1b86ded5eb6dd29593c8c6ea3fa5adf Mon Sep 17 00:00:00 2001 From: satyam Date: Fri, 4 Jan 2019 19:14:17 +0530 Subject: [PATCH 1/7] basic class implementation of ERC1410 --- contracts/ERC1410/ERC1410Basic.sol | 139 +++++++++++++++++++++++++ contracts/ERC1410/IERC1410.sol | 52 +++++++++ contracts/ERC1594/ERC1594.sol | 2 +- contracts/{ERC1594 => }/ERC20Token.sol | 0 4 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 contracts/ERC1410/ERC1410Basic.sol create mode 100644 contracts/ERC1410/IERC1410.sol rename contracts/{ERC1594 => }/ERC20Token.sol (100%) diff --git a/contracts/ERC1410/ERC1410Basic.sol b/contracts/ERC1410/ERC1410Basic.sol new file mode 100644 index 0000000..ade33a8 --- /dev/null +++ b/contracts/ERC1410/ERC1410Basic.sol @@ -0,0 +1,139 @@ +pragma solidity ^0.4.24; + +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../math/KindMath.sol"; + +contract ERC1410Basic { + + using SafeMath for uint256; + + // Represents a fungible set of tokens. + struct Partition { + uint256 amount; + bytes32 partition; + } + + uint256 _totalSupply; + + // 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 (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 _operator, + address indexed _from, + address indexed _to, + uint256 _value, + bytes _data, + bytes _operatorData + ); + + /** + * @dev Total number of tokens in existence + */ + function totalSupply() external view returns (uint256) { + return _totalSupply; + } + + /// @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) external view returns (uint256) { + balances[_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) external view returns (uint256) { + if (_validPartition(_partition, _tokenHolder)) + return partitions[_tokenHolder][partitionToIndex[_tokenHolder][_partition]].amount; + else + return 0; + } + + /// @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[]) { + 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 Transfers the ownership of tokens from a specified partition from one address to another address + /// @param _partition The partition from which to transfer tokens + /// @param _to The address to which to transfer tokens to + /// @param _value The amount of tokens to transfer from `_partition` + /// @param _data Additional data attached to the transfer of tokens + /// @return The partition to which the transferred tokens were allocated for the _to address + function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32) { + // Add a function to verify the `_data` parameter + require(_validPartition(_partition, msg.sender), "Invalid partition"); + require(partitions[msg.sender][partitionToIndex[msg.sender][_partition]].amount >= _value, "Insufficient balance"); + require(_to != address(0), "0x address not allowed"); + // TODO: Need to create the bytes division of the `_partition` so it can be easily findout in which receiver's partition + // token will transfered. For current implementation we are assuming that the receiver's partition will be same as sender's + // as well as it also pass the `_validPartition()` check. + // Note- There is no operator used for the execution of this call so `_operator` value in + // in event is address(0) same for the `_operatorData` + _transferByPartition(msg.sender, _to, _value, _partition, _data, address(0), "0x0"); + } + + /// @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` + /// @param _data Additional data attached to the transfer of tokens + /// @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, bytes _data) external view returns (byte, bytes32, bytes32) { + // TODO: Applied the check over the `_data` parameter + if (!_validPartition(_partition, _from)) + return (0x50, "Partition not exists", "0x0"); + else if (partitions[_from][partitionToIndex[_from][_partition]].amount < _value) + return (0x52, "Insufficent balance", "0x0"); + else if (_to == address(0)) + return (0x57, "Invalid receiver", "0x0"); + else if (!KindMath.checkSub(balances[_from], _value) || !KindMath.checkAdd(balances[_to], _value)) + return (0x50, "Overflow", "0x0"); + + // 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, bytes _data, address _operator, bytes _operatorData) internal { + uint256 _fromIndex = partitionToIndex[_from][_partition]; + uint256 _toIndex = partitionToIndex[_to][_partition]; + // 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); + // Emit transfer event. + emit TransferByPartition(_partition, _operator, _from, _to, _value, _data, _operatorData); + } + + 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; + } + + + +} \ No newline at end of file diff --git a/contracts/ERC1410/IERC1410.sol b/contracts/ERC1410/IERC1410.sol new file mode 100644 index 0000000..2a8b128 --- /dev/null +++ b/contracts/ERC1410/IERC1410.sol @@ -0,0 +1,52 @@ +pragma solidity ^0.4.24; + +interface IERC1410 { + + // Token Information + function balanceOf(address _tokenHolder) external view returns (uint256); + function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256); + function partitionsOf(address _tokenHolder) external view returns (bytes32[]); + function totalSupply() external view returns (uint256); + + // Token Transfers + function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32); + function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32); + function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32); + + // Operator Information + function isOperator(address _operator, address _tokenHolder) external view returns (bool); + function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool); + + // Operator Management + function authorizeOperator(address _operator) external; + function revokeOperator(address _operator) external; + function authorizeOperatorByPartition(bytes32 _partition, address _operator) external; + function revokeOperatorByPartition(bytes32 _partition, address _operator) external; + + // Issuance / Redemption + function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external; + function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external; + function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _operatorData) external; + + // Transfer Events + event TransferByPartition( + bytes32 indexed _fromPartition, + address _operator, + address indexed _from, + address indexed _to, + uint256 _value, + bytes _data, + bytes _operatorData + ); + + // Operator Events + event AuthorizedOperator(address indexed operator, address indexed tokenHolder); + event RevokedOperator(address indexed operator, address indexed tokenHolder); + event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + + // Issuance / Redemption Events + event IssuedByPartition(bytes32 indexed partition, address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); + event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 amount, bytes operatorData); + +} \ No newline at end of file diff --git a/contracts/ERC1594/ERC1594.sol b/contracts/ERC1594/ERC1594.sol index d34c193..e739d66 100644 --- a/contracts/ERC1594/ERC1594.sol +++ b/contracts/ERC1594/ERC1594.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.24; import "./IERC1594.sol"; -import "./ERC20Token.sol"; +import "../ERC20Token.sol"; import "../math/KindMath.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; diff --git a/contracts/ERC1594/ERC20Token.sol b/contracts/ERC20Token.sol similarity index 100% rename from contracts/ERC1594/ERC20Token.sol rename to contracts/ERC20Token.sol From 351be6434573bee6db77283fa3f4ddfa04be68ef Mon Sep 17 00:00:00 2001 From: satyam Date: Sat, 5 Jan 2019 20:50:54 +0530 Subject: [PATCH 2/7] operator class addition --- contracts/ERC1410/ERC1410Basic.sol | 7 ++-- contracts/ERC1410/ERC1410Operator.sol | 53 +++++++++++++++++++++++++++ contracts/ERC1410/IERC1410.sol | 22 +++++------ 3 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 contracts/ERC1410/ERC1410Operator.sol diff --git a/contracts/ERC1410/ERC1410Basic.sol b/contracts/ERC1410/ERC1410Basic.sol index ade33a8..9b69759 100644 --- a/contracts/ERC1410/ERC1410Basic.sol +++ b/contracts/ERC1410/ERC1410Basic.sol @@ -79,9 +79,6 @@ contract ERC1410Basic { /// @return The partition to which the transferred tokens were allocated for the _to address function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32) { // Add a function to verify the `_data` parameter - require(_validPartition(_partition, msg.sender), "Invalid partition"); - require(partitions[msg.sender][partitionToIndex[msg.sender][_partition]].amount >= _value, "Insufficient balance"); - require(_to != address(0), "0x address not allowed"); // TODO: Need to create the bytes division of the `_partition` so it can be easily findout in which receiver's partition // token will transfered. For current implementation we are assuming that the receiver's partition will be same as sender's // as well as it also pass the `_validPartition()` check. @@ -116,6 +113,10 @@ contract ERC1410Basic { } function _transferByPartition(address _from, address _to, uint256 _value, bytes32 _partition, bytes _data, address _operator, bytes _operatorData) internal { + require(_validPartition(_partition, _from), "Invalid partition"); + require(partitions[_from][partitionToIndex[_from][_partition]].amount >= _value, "Insufficient balance"); + require(_to != address(0), "0x address not allowed"); + uint256 _fromIndex = partitionToIndex[_from][_partition]; uint256 _toIndex = partitionToIndex[_to][_partition]; // Changing the state values diff --git a/contracts/ERC1410/ERC1410Operator.sol b/contracts/ERC1410/ERC1410Operator.sol new file mode 100644 index 0000000..52ed103 --- /dev/null +++ b/contracts/ERC1410/ERC1410Operator.sol @@ -0,0 +1,53 @@ +pragma solidity ^0.4.24; + +import "./ERC1410Operator.sol"; + +contract ERC1410Operator is ERC1410Basic { + + // Mapping from (investor, tranche, operator) to approved status + mapping (address => mapping (bytes32 => mapping (address => bool))) partitionApprovals; + + // Mapping from (investor, operator) to approved status (can be used against any tranches) + mapping (address => mapping (address => bool)) approvals; + + event AuthorizedOperator(address indexed operator, address indexed tokenHolder); + event RevokedOperator(address indexed operator, address indexed tokenHolder); + + event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + + function isOperator(address _operator, address _tokenHolder) external view returns (bool) { + return approvals[msg.sender][_operator]; + } + + function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool) { + return partitionApprovals[msg.sender][_partition][_operator]; + } + + // Operator Management + function authorizeOperator(address _operator) external { + approvals[msg.sender][_operator] = true; + emit AuthorizedOperator(_operator, msg.sender); + } + function revokeOperator(address _operator) external { + approvals[msg.sender][_operator] = false; + emit RevokedOperator(operator, msg.sender); + } + function authorizeOperatorByPartition(bytes32 _partition, address _operator) external { + partitionApprovals[msg.sender][_partition][_operator] = true; + emit AuthorizedOperatorByPartition(_partition, _operator, msg.sender); + } + + function revokeOperatorByPartition(bytes32 _partition, address _operator) external { + partitionApprovals[msg.sender][_partition][_operator] = false; + emit RevokedOperatorByPartition(_partition, _operator, msg.sender); + } + + function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32) { + // TODO: Add a functionality of verifying the `_operatorData` + // TODO: Add a functionality of verifying the `_data` + require(partitionApprovals[_from][_partition][msg.sender], "Not authorised"); + _transferByPartition(_from, _to, _value, _partition, _data, msg.sender, _operatorData); + } + +} \ No newline at end of file diff --git a/contracts/ERC1410/IERC1410.sol b/contracts/ERC1410/IERC1410.sol index 2a8b128..76b6564 100644 --- a/contracts/ERC1410/IERC1410.sol +++ b/contracts/ERC1410/IERC1410.sol @@ -10,18 +10,18 @@ interface IERC1410 { // Token Transfers function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32); - function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32); +--- function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32); function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32); // Operator Information - function isOperator(address _operator, address _tokenHolder) external view returns (bool); - function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool); +--- function isOperator(address _operator, address _tokenHolder) external view returns (bool); +--- function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool); // Operator Management - function authorizeOperator(address _operator) external; - function revokeOperator(address _operator) external; - function authorizeOperatorByPartition(bytes32 _partition, address _operator) external; - function revokeOperatorByPartition(bytes32 _partition, address _operator) external; +--- function authorizeOperator(address _operator) external; +--- function revokeOperator(address _operator) external; +--- function authorizeOperatorByPartition(bytes32 _partition, address _operator) external; +--- function revokeOperatorByPartition(bytes32 _partition, address _operator) external; // Issuance / Redemption function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external; @@ -40,10 +40,10 @@ interface IERC1410 { ); // Operator Events - event AuthorizedOperator(address indexed operator, address indexed tokenHolder); - event RevokedOperator(address indexed operator, address indexed tokenHolder); - event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); - event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); +--- event AuthorizedOperator(address indexed operator, address indexed tokenHolder); +--- event RevokedOperator(address indexed operator, address indexed tokenHolder); +--- event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); +--- event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); // Issuance / Redemption Events event IssuedByPartition(bytes32 indexed partition, address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); From 71f8946dd77d10b1b99897a4fa9f084d96ff87c8 Mon Sep 17 00:00:00 2001 From: satyam Date: Mon, 7 Jan 2019 12:21:27 +0530 Subject: [PATCH 3/7] cleaning and addition of standard class --- contracts/ERC1410/ERC1410Operator.sol | 55 +++++- contracts/ERC1410/ERC1410Standard.sol | 72 +++++++ contracts/ERC1410/IERC1410.sol | 28 +-- contracts/PartialFungibleToken.sol | 273 -------------------------- contracts/interfaces/IERC1410.sol | 148 -------------- eip/eip-1410.md | 4 +- 6 files changed, 133 insertions(+), 447 deletions(-) create mode 100644 contracts/ERC1410/ERC1410Standard.sol delete mode 100644 contracts/PartialFungibleToken.sol delete mode 100644 contracts/interfaces/IERC1410.sol diff --git a/contracts/ERC1410/ERC1410Operator.sol b/contracts/ERC1410/ERC1410Operator.sol index 52ed103..852c4cb 100644 --- a/contracts/ERC1410/ERC1410Operator.sol +++ b/contracts/ERC1410/ERC1410Operator.sol @@ -1,13 +1,13 @@ pragma solidity ^0.4.24; -import "./ERC1410Operator.sol"; +import "./ERC1410Basic.sol"; contract ERC1410Operator is ERC1410Basic { - // Mapping from (investor, tranche, operator) to approved status + // Mapping from (investor, partition, operator) to approved status mapping (address => mapping (bytes32 => mapping (address => bool))) partitionApprovals; - // Mapping from (investor, operator) to approved status (can be used against any tranches) + // Mapping from (investor, operator) to approved status (can be used against any partition) mapping (address => mapping (address => bool)) approvals; event AuthorizedOperator(address indexed operator, address indexed tokenHolder); @@ -16,37 +16,72 @@ contract ERC1410Operator is ERC1410Basic { event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); - function isOperator(address _operator, address _tokenHolder) external view returns (bool) { - return approvals[msg.sender][_operator]; + /// @notice Determines whether `_operator` is an operator for all partitions of `_tokenHolder` + /// @param _operator The operator to check + /// @param _tokenHolder The token holder to check + /// @return Whether the `_operator` is an operator for all partitions of `_tokenHolder` + function isOperator(address _operator, address _tokenHolder) public view returns (bool) { + return approvals[_tokenHolder][_operator]; } - function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool) { - return partitionApprovals[msg.sender][_partition][_operator]; + /// @notice Determines whether `_operator` is an operator for a specified partition of `_tokenHolder` + /// @param _partition The partition to check + /// @param _operator The operator to check + /// @param _tokenHolder The token holder to check + /// @return Whether the `_operator` is an operator for a specified partition of `_tokenHolder` + function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) public view returns (bool) { + return partitionApprovals[_tokenHolder][_partition][_operator]; } - // Operator Management + /////////////////////// + /// Operator Management + /////////////////////// + + /// @notice Authorises an operator for all partitions of `msg.sender` + /// @param _operator An address which is being authorised function authorizeOperator(address _operator) external { approvals[msg.sender][_operator] = true; emit AuthorizedOperator(_operator, msg.sender); } + + /// @notice Revokes authorisation of an operator previously given for all partitions of `msg.sender` + /// @param _operator An address which is being de-authorised function revokeOperator(address _operator) external { approvals[msg.sender][_operator] = false; - emit RevokedOperator(operator, msg.sender); + emit RevokedOperator(_operator, msg.sender); } + + /// @notice Authorises an operator for a given partition of `msg.sender` + /// @param _partition The partition to which the operator is authorised + /// @param _operator An address which is being authorised function authorizeOperatorByPartition(bytes32 _partition, address _operator) external { partitionApprovals[msg.sender][_partition][_operator] = true; emit AuthorizedOperatorByPartition(_partition, _operator, msg.sender); } + /// @notice Revokes authorisation of an operator previously given for a specified partition of `msg.sender` + /// @param _partition The partition to which the operator is de-authorised + /// @param _operator An address which is being de-authorised function revokeOperatorByPartition(bytes32 _partition, address _operator) external { partitionApprovals[msg.sender][_partition][_operator] = false; emit RevokedOperatorByPartition(_partition, _operator, msg.sender); } + /// @notice Transfers the ownership of tokens from a specified partition from one address to another address + /// @param _partition The partition from which to transfer tokens + /// @param _from The address from which to transfer tokens from + /// @param _to The address to which to transfer tokens to + /// @param _value The amount of tokens to transfer from `_partition` + /// @param _data Additional data attached to the transfer of tokens + /// @param _operatorData Additional data attached to the transfer of tokens by the operator + /// @return The partition to which the transferred tokens were allocated for the _to address function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32) { // TODO: Add a functionality of verifying the `_operatorData` // TODO: Add a functionality of verifying the `_data` - require(partitionApprovals[_from][_partition][msg.sender], "Not authorised"); + require( + isOperator(msg.sender, _from) || isOperatorForPartition(_partition, msg.sender, _from), + "Not authorised" + ); _transferByPartition(_from, _to, _value, _partition, _data, msg.sender, _operatorData); } diff --git a/contracts/ERC1410/ERC1410Standard.sol b/contracts/ERC1410/ERC1410Standard.sol new file mode 100644 index 0000000..81fe1cc --- /dev/null +++ b/contracts/ERC1410/ERC1410Standard.sol @@ -0,0 +1,72 @@ +pragma solidity ^0.4.24; + +import "./ERC1410Operator.sol"; +import "./IERC1410.sol"; +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; + +contract ERC1410Standard is IERC1410, ERC1410Operator, Ownable { + + /// @notice Increases totalSupply and the corresponding amount of the specified owners partition + /// @param _partition The partition to allocate the increase in balance + /// @param _tokenHolder The token holder whose balance should be increased + /// @param _value The amount by which to increase the balance + /// @param _data Additional data attached to the minting of tokens + function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external onlyOwner { + // Add the function to validate the `_data` parameter + _validateParams(_partition, _value); + require(_tokenHolder != address(0), "Invalid token receiver"); + uint256 index = partitionToIndex[_tokenHolder][_partition]; + if (index == 0) { + partitions[_tokenHolder].push(Partition(_value, _partition)); + partitionToIndex[_tokenHolder][_partition] = partitions[_tokenHolder].length; + } + _totalSupply = _totalSupply.add(_value); + balances[_tokenHolder] = balances[_tokenHolder].add(_value); + emit IssuedByPartition(_partition, _tokenHolder, _value, _data); + } + + /// @notice Decreases totalSupply and the corresponding amount of the specified partition of msg.sender + /// @param _partition The partition to allocate the decrease in balance + /// @param _value The amount by which to decrease the balance + /// @param _data Additional data attached to the burning of tokens + function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external { + // Add the function to validate the `_data` parameter + _redeemByPartition(_partition, msg.sender, address(0), _value, _data, "0x0"); + } + + /// @notice Decreases totalSupply and the corresponding amount of the specified partition of tokenHolder + /// @dev This function can only be called by the authorised operator. + /// @param _partition The partition to allocate the decrease in balance. + /// @param _tokenHolder The token holder whose balance should be decreased + /// @param _value The amount by which to decrease the balance + /// @param _data Additional data attached to the burning of tokens + /// @param _operatorData Additional data attached to the transfer of tokens by the operator + function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data, bytes _operatorData) external { + // Add the function to validate the `_data` parameter + // TODO: Add a functionality of verifying the `_operatorData` + require(_tokenHolder != address(0), "Invalid from address"); + require( + isOperator(msg.sender, _tokenHolder) || isOperatorForPartition(_partition, msg.sender, _tokenHolder), + "Not authorised" + ); + _redeemByPartition(_partition, _tokenHolder, msg.sender, _value, _data, _operatorData); + } + + function _redeemByPartition(bytes32 _partition, address _from, address _operator, uint256 _value, bytes _data, bytes _operatorData) internal { + // Add the function to validate the `_data` parameter + _validateParams(_partition, _value); + require(_validPartition(_partition, _from), "Invalid partition"); + uint256 index = partitionToIndex[_from][_partition] - 1; + require(partitions[_from][index].amount >= _value, "Insufficient value"); + partitions[_from][index].amount = partitions[_from][index].amount.sub(_value); + balances[_from] = balances[_from].sub(_value); + _totalSupply = _totalSupply.sub(_value); + emit RedeemedByPartition(_partition, _operator, _from, _value, _data, _operatorData); + } + + function _validateParams(bytes32 _partition, uint256 _value) internal { + require(_value != uint256(0), "Zero value not allowed"); + require(_partition != bytes32(0), "Invalid partition"); + } + +} \ No newline at end of file diff --git a/contracts/ERC1410/IERC1410.sol b/contracts/ERC1410/IERC1410.sol index 76b6564..91750ac 100644 --- a/contracts/ERC1410/IERC1410.sol +++ b/contracts/ERC1410/IERC1410.sol @@ -10,23 +10,23 @@ interface IERC1410 { // Token Transfers function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32); ---- function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32); + function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32); function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32); // Operator Information ---- function isOperator(address _operator, address _tokenHolder) external view returns (bool); ---- function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool); + function isOperator(address _operator, address _tokenHolder) external view returns (bool); + function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool); // Operator Management ---- function authorizeOperator(address _operator) external; ---- function revokeOperator(address _operator) external; ---- function authorizeOperatorByPartition(bytes32 _partition, address _operator) external; ---- function revokeOperatorByPartition(bytes32 _partition, address _operator) external; + function authorizeOperator(address _operator) external; + function revokeOperator(address _operator) external; + function authorizeOperatorByPartition(bytes32 _partition, address _operator) external; + function revokeOperatorByPartition(bytes32 _partition, address _operator) external; // Issuance / Redemption function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external; function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external; - function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _operatorData) external; + function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data, bytes _operatorData) external; // Transfer Events event TransferByPartition( @@ -40,13 +40,13 @@ interface IERC1410 { ); // Operator Events ---- event AuthorizedOperator(address indexed operator, address indexed tokenHolder); ---- event RevokedOperator(address indexed operator, address indexed tokenHolder); ---- event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); ---- event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + event AuthorizedOperator(address indexed operator, address indexed tokenHolder); + event RevokedOperator(address indexed operator, address indexed tokenHolder); + event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); + event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); // Issuance / Redemption Events - event IssuedByPartition(bytes32 indexed partition, address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); - event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 amount, bytes operatorData); + event IssuedByPartition(bytes32 indexed partition, address indexed to, uint256 value, bytes data); + event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 value, bytes data, bytes operatorData); } \ No newline at end of file diff --git a/contracts/PartialFungibleToken.sol b/contracts/PartialFungibleToken.sol deleted file mode 100644 index e64f2c8..0000000 --- a/contracts/PartialFungibleToken.sol +++ /dev/null @@ -1,273 +0,0 @@ -pragma solidity ^0.4.24; - -import "./interfaces/IERC1410.sol"; - -/** - * @title Reference implementation of partially-fungible tokens - */ -contract PartialFungibleToken is IERC1410 { - - // Represents a fungible set of tokens. - struct Tranche { - uint256 amount; - bytes32 tranche; - } - - uint256 public totalSupply; - - string public name; - - string public symbol; - - // Mapping from investor to aggregated balance across all investor token sets - mapping (address => uint256) balances; - - // Mapping from investor to their tranches - mapping (address => Tranche[]) tranches; - - // Mapping from (investor, tranche) to index of corresponding tranche in tranches - mapping (address => mapping (bytes32 => uint256)) trancheToIndex; - - // Mapping from (investor, tranche, operator) to approved status - mapping (address => mapping (bytes32 => mapping (address => bool))) trancheApprovals; - - // Mapping from (investor, operator) to approved status (can be used against any tranches) - mapping (address => mapping (address => bool)) approvals; - - /// @notice Counts the sum of all tranche balances assigned to an owner - /// @param _owner An address for whom to query the balance - /// @return The number of tokens owned by `_owner`, possibly zero - function balanceOf(address _owner) external view returns (uint256) { - return balances[_owner]; - } - - /// @notice Counts the balance associated with a specific tranche assigned to an owner - /// @param _tranche The tranche for which to query the balance - /// @param _owner An address for whom to query the balance - /// @return The number of tokens owned by `_owner` with the metadata associated with `_tranche`, possibly zero - function balanceOfTranche(bytes32 _tranche, address _owner) external view returns (uint256) { - return tranches[_owner][trancheToIndex[_owner][_tranche]].amount; - } - - /// @notice Transfers the ownership of tokens from a specified tranche from one address to another address - /// @param _tranche The tranche from which to transfer tokens - /// @param _to The address to which to transfer tokens to - /// @param _amount The amount of tokens to transfer from `_tranche` - /// @param _data Additional data attached to the transfer of tokens - /// @return A reason code related to the success of the send operation - /// @return The tranche to which the transferred tokens were allocated for the _to address - function sendTranche(bytes32 _tranche, address _to, uint256 _amount, bytes _data) external returns (byte, bytes32) { - (byte reason, bytes32 newTranche) = _sendTranche(msg.sender, _to, _amount, _tranche, _data, ''); - emit SentTranche( - address(0), - msg.sender, - _to, - _tranche, - newTranche, - _amount, - _data, - '' - ); - return (reason, newTranche); - } - - function _sendTranche(address _from, address _to, uint256 _amount, bytes32 _tranche, bytes _data, bytes _operatorData) internal returns (byte, bytes32) { - - if (tranches[_from][trancheToIndex[_from][_tranche]].amount < _amount) { - return (hex"00", bytes32("")); - } - // Checking the overflow condition in subtraction TODO: Create a library for that similar to SafeMath - if (tranches[_from][trancheToIndex[_from][_tranche]].amount > tranches[_from][trancheToIndex[_from][_tranche]].amount - _amount) { - return (hex"10", bytes32("")); - } - - // Checking the overflow condition in addition TODO: Create a library for that similar to SafeMath - if (tranches[_to][trancheToIndex[_to][_tranche]].amount > tranches[_to][trancheToIndex[_to][_tranche]].amount + _amount) { - return (hex"10", bytes32("")); - } - - tranches[_from][trancheToIndex[_from][_tranche]].amount = tranches[_from][trancheToIndex[_from][_tranche]].amount - _amount; - balances[_from] = balances[_from] - _amount; - tranches[_to][trancheToIndex[_to][_tranche]].amount = tranches[_to][trancheToIndex[_to][_tranche]].amount + _amount; - balances[_to] = balances[_to] + _amount; - - // TODO: If transferring to a registered contract, call its callback function - - return (hex"01", _tranche); - - } - - /// @notice Transfers the ownership of tokens from a specified tranche from one address to another address - /// @param _from The address from which to transfer tokens from - /// @param _to The address to which to transfer tokens to - /// @param _tranche The tranche from which to transfer tokens - /// @param _amount The amount of tokens to transfer from `_tranche` - /// @param _data Additional data attached to the transfer of tokens - /// @param _operatorData Additional data attached to the transfer of tokens by the operator - /// @return A reason code related to the success of the send operation - /// @return The tranche to which the transferred tokens were allocated for the _to address - function operatorSendTranche(bytes32 _tranche, address _from, address _to, uint256 _amount, bytes _data, bytes _operatorData) external returns (byte, bytes32) { - // Check operator is approved - if ((!trancheApprovals[_from][_tranche][msg.sender]) && (!approvals[_from][msg.sender])) { - return (hex"20", bytes32("")); - } - (byte reason, bytes32 newTranche) = _sendTranche(_from, _to, _amount, _tranche, _data, _operatorData); - emit SentTranche( - msg.sender, - _from, - _to, - _tranche, - newTranche, - _amount, - _data, - _operatorData - ); - return (reason, newTranche); - } - - /// @notice Allows enumeration over an individual owners tranches - /// @param _owner An address over which to enumerate tranches - /// @param _index The index of the tranche - /// @return The tranche key corresponding to `_index` - function trancheByIndex(address _owner, uint256 _index) external view returns (bytes32) { - return tranches[_owner][_index].tranche; - } - - /// @notice Enables caller to determine the count of tranches owned by an address - /// @param _owner An address over which to enumerate tranches - /// @return The number of tranches owned by an `_owner` - function tranchesOf(address _owner) external view returns (uint256) { - return tranches[_owner].length; - } - - /// @notice Defines a list of operators which can operate over all addresses and tranches - /// @return The list of default operators - function defaultOperators() public view returns (address[]) { - // No default operators - return new address[](0); - } - - /// @notice Defines a list of operators which can operate over all addresses for the specified tranche - /// @return The list of default operators for `_tranche` - function defaultOperatorsTranche(bytes32 _tranche) public view returns (address[]) { - // No default operators - return new address[](0); - } - - - /// @notice Authorises an operator for all tranches of `msg.sender` - /// @param _operator An address which is being authorised - function authorizeOperator(address _operator) public { - approvals[msg.sender][_operator] = true; - emit AuthorizedOperator(_operator, msg.sender); - } - - /// @notice Authorises an operator for a given tranche of `msg.sender` - /// @param _tranche The tranche to which the operator is authorised - /// @param _operator An address which is being authorised - function authorizeOperatorTranche(bytes32 _tranche, address _operator) public { - trancheApprovals[msg.sender][_tranche][_operator] = true; - emit AuthorizedOperatorTranche(_tranche, _operator, msg.sender); - } - - /// @notice Revokes authorisation of an operator previously given for all tranches of `msg.sender` - /// @param _operator An address which is being de-authorised - function revokeOperator(address _operator) public { - approvals[msg.sender][_operator] = false; - emit RevokedOperator(_operator, msg.sender); - } - - /// @notice Revokes authorisation of an operator previously given for a specified tranche of `msg.sender` - /// @param _tranche The tranche to which the operator is de-authorised - /// @param _operator An address which is being de-authorised - function revokeOperatorTranche(bytes32 _tranche, address _operator) public { - trancheApprovals[msg.sender][_tranche][_operator] = false; - emit RevokedOperatorTranche(_tranche, _operator, msg.sender); - } - - /// @notice Determines whether `_operator` is an operator for all tranches of `_owner` - /// @param _operator The operator to check - /// @param _owner The owner to check - /// @return Whether the `_operator` is an operator for all tranches of `_owner` - function isOperatorFor(address _operator, address _owner) public view returns (bool) { - return approvals[_owner][_operator]; - } - - /// @notice Determines whether `_operator` is an operator for a specified tranche of `_owner` - /// @param _tranche The tranche to check - /// @param _operator The operator to check - /// @param _owner The owner to check - /// @return Whether the `_operator` is an operator for a specified tranche of `_owner` - function isOperatorForTranche(bytes32 _tranche, address _operator, address _owner) public view returns (bool) { - return trancheApprovals[_owner][_tranche][_operator]; - } - - /// @notice Increases totalSupply and the corresponding amount of the specified owners tranche - /// @param _tranche The tranche to allocate the increase in balance - /// @param _owner The owner whose balance should be increased - /// @param _amount The amount by which to increase the balance - /// @param _data Additional data attached to the minting of tokens - /// @return A reason code related to the success of the mint operation - function mint(bytes32 _tranche, address _owner, uint256 _amount, bytes _data) public returns (byte reason) { - // TODO: Apply the check for Authorization of Mint function - if (tranches[_owner][trancheToIndex[_owner][_tranche]].amount + _amount < tranches[_owner][trancheToIndex[_owner][_tranche]].amount) { - return (hex"10"); - } - if (balances[_owner] + _amount < balances[_owner]) { - return (hex"10"); - } - if (totalSupply + _amount < totalSupply) { - return (hex"10"); - } - tranches[_owner][trancheToIndex[_owner][_tranche]].amount = tranches[_owner][trancheToIndex[_owner][_tranche]].amount + _amount; - balances[_owner] = balances[_owner] + _amount; - totalSupply = totalSupply + _amount; - emit Minted(_owner, _tranche, _amount, _data); - emit SentTranche( - msg.sender, - address(0), - _owner, - bytes32(""), - _tranche, - _amount, - _data, - '' - ); - return hex"01"; - } - - /// @notice Decreases totalSupply and the corresponding amount of the specified owners tranche - /// @param _tranche The tranche to allocate the decrease in balance - /// @param _owner The owner whose balance should be decreased - /// @param _amount The amount by which to decrease the balance - /// @param _data Additional data attached to the burning of tokens - /// @return A reason code related to the success of the burn operation - function burn(bytes32 _tranche, address _owner, uint256 _amount, bytes _data) public returns (byte reason) { - // TODO: Apply the check for Authorization of burn function - if (tranches[_owner][trancheToIndex[_owner][_tranche]].amount - _amount > tranches[_owner][trancheToIndex[_owner][_tranche]].amount) { - return (hex"10"); - } - if (balances[_owner] - _amount > balances[_owner]) { - return (hex"10"); - } - if (totalSupply - _amount > totalSupply) { - return (hex"10"); - } - tranches[_owner][trancheToIndex[_owner][_tranche]].amount = tranches[_owner][trancheToIndex[_owner][_tranche]].amount - _amount; - balances[_owner] = balances[_owner] - _amount; - totalSupply = totalSupply - _amount; - emit Burnt(_owner, _tranche, _amount, _data); - emit SentTranche( - msg.sender, - _owner, - address(0), - _tranche, - bytes32(""), - _amount, - _data, - '' - ); - return hex"01"; - } - -} diff --git a/contracts/interfaces/IERC1410.sol b/contracts/interfaces/IERC1410.sol deleted file mode 100644 index 7d24989..0000000 --- a/contracts/interfaces/IERC1410.sol +++ /dev/null @@ -1,148 +0,0 @@ -pragma solidity ^0.4.24; - -/** - * @title Interface of ERC1410 standard - * @dev ref. https://github.com/ethereum/EIPs/issues/1410 - * @dev ERC1410 is dependent on the ERC777 - */ - - interface IERC1410 { - - /// @notice A descriptive name for tokens in this contract - function name() external view returns (string _name); - - /// @notice An abbreviated name for tokens in this contract - function symbol() external view returns (string _symbol); - - /// @notice Counts the sum of all tranche balances assigned to an owner - /// @param _owner An address for whom to query the balance - /// @return The number of tokens owned by `_owner`, possibly zero - function balanceOf(address _owner) external view returns (uint256); - - /// @notice Counts the balance associated with a specific tranche assigned to an owner - /// @param _tranche The tranche for which to query the balance - /// @param _owner An address for whom to query the balance - /// @return The number of tokens owned by `_owner` with the metadata associated with `_tranche`, possibly zero - function balanceOfTranche(bytes32 _tranche, address _owner) external view returns (uint256); - - /// @notice Count all tokens tracked by this contract - /// @return A count of all tokens tracked by this contract - function totalSupply() external view returns (uint256); - - /// @notice Transfers the ownership of tokens from a specified tranche from one address to another address - /// @param _tranche The tranche from which to transfer tokens - /// @param _to The address to which to transfer tokens to - /// @param _amount The amount of tokens to transfer from `_tranche` - /// @param _data Additional data attached to the transfer of tokens - /// @return A reason code related to the success of the send operation - /// @return The tranche to which the transferred tokens were allocated for the _to address - function sendTranche(bytes32 _tranche, address _to, uint256 _amount, bytes _data) external returns (byte, bytes32); - - /// @notice Transfers the ownership of tokens from a specified tranche from one address to another address - /// @param _tranche The tranche from which to transfer tokens - /// @param _from The address from which to transfer tokens from - /// @param _to The address to which to transfer tokens to - /// @param _amount The amount of tokens to transfer from `_tranche` - /// @param _data Additional data attached to the transfer of tokens - /// @param _operatorData Additional data attached to the transfer of tokens by the operator - /// @return A reason code related to the success of the send operation - /// @return The tranche to which the transferred tokens were allocated for the _to address - function operatorSendTranche(bytes32 _tranche, address _from, address _to, uint256 _amount, bytes _data, bytes _operatorData) external returns (byte, bytes32); - - /// @notice Allows enumeration over an individual owners tranches - /// @param _owner An address over which to enumerate tranches - /// @param _index The index of the tranche - /// @return The tranche key corresponding to `_index` - function trancheByIndex(address _owner, uint256 _index) external view returns (bytes32); - - /// @notice Enables caller to determine the count of tranches owned by an address - /// @param _owner An address over which to enumerate tranches - /// @return The number of tranches owned by an `_owner` - function tranchesOf(address _owner) external view returns (uint256); - - /// @notice Defines a list of operators which can operate over all addresses and tranches - /// @return The list of default operators - function defaultOperators() public view returns (address[]); - - /// @notice Defines a list of operators which can operate over all addresses for the specified tranche - /// @return The list of default operators for `_tranche` - function defaultOperatorsTranche(bytes32 _tranche) public view returns (address[]); - - /// @notice Authorises an operator for all tranches of `msg.sender` - /// @param _operator An address which is being authorised - function authorizeOperator(address _operator) public; - - /// @notice Authorises an operator for a given tranche of `msg.sender` - /// @param _tranche The tranche to which the operator is authorised - /// @param _operator An address which is being authorised - function authorizeOperatorTranche(bytes32 _tranche, address _operator) public; - - /// @notice Revokes authorisation of an operator previously given for all tranches of `msg.sender` - /// @param _operator An address which is being de-authorised - function revokeOperator(address _operator) public; - - /// @notice Revokes authorisation of an operator previously given for a specified tranche of `msg.sender` - /// @param _tranche The tranche to which the operator is de-authorised - /// @param _operator An address which is being de-authorised - function revokeOperatorTranche(bytes32 _tranche, address _operator) public; - - /// @notice Determines whether `_operator` is an operator for all tranches of `_owner` - /// @param _operator The operator to check - /// @param _owner The owner to check - /// @return Whether the `_operator` is an operator for all tranches of `_owner` - function isOperatorFor(address _operator, address _owner) public view returns (bool); - - /// @notice Determines whether `_operator` is an operator for a specified tranche of `_owner` - /// @param _tranche The tranche to check - /// @param _operator The operator to check - /// @param _owner The owner to check - /// @return Whether the `_operator` is an operator for a specified tranche of `_owner` - function isOperatorForTranche(bytes32 _tranche, address _operator, address _owner) public view returns (bool); - - /// @notice Increases totalSupply and the corresponding amount of the specified owners tranche - /// @param _tranche The tranche to allocate the increase in balance - /// @param _owner The owner whose balance should be increased - /// @param _amount The amount by which to increase the balance - /// @param _data Additional data attached to the minting of tokens - /// @return A reason code related to the success of the mint operation - function mint(bytes32 _tranche, address _owner, uint256 _amount, bytes _data) public returns (byte reason); - - /// @notice Decreases totalSupply and the corresponding amount of the specified owners tranche - /// @param _tranche The tranche to allocate the decrease in balance - /// @param _owner The owner whose balance should be decreased - /// @param _amount The amount by which to decrease the balance - /// @param _data Additional data attached to the burning of tokens - /// @return A reason code related to the success of the burn operation - function burn(bytes32 _tranche, address _owner, uint256 _amount, bytes _data) public returns (byte reason); - - /// @notice This emits on any successful call to `mint` - event Minted(address indexed owner, bytes32 tranche, uint256 amount, bytes data); - - /// @notice This emits on any successful call to `burn` - event Burnt(address indexed owner, bytes32 tranche, uint256 amount, bytes data); - - /// @notice This emits on any successful transfer or minting of tokens - event SentTranche( - address indexed operator, - address indexed from, - address indexed to, - bytes32 fromTranche, - bytes32 toTranche, - uint256 amount, - bytes data, - bytes operatorData - ); - - /// @notice This emits on any successful operator approval for all tranches, excluding default operators - event AuthorizedOperator(address indexed operator, address indexed owner); - - /// @notice This emits on any successful operator approval for a single tranche, excluding default tranche operators - event AuthorizedOperatorTranche(bytes32 indexed tranche, address indexed operator, address indexed owner); - - /// @notice This emits on any successful revoke of an operators approval for all tranches - event RevokedOperator(address indexed operator, address indexed owner); - - /// @notice This emits on any successful revoke of an operators approval for a single tranche - event RevokedOperatorTranche(bytes32 indexed tranche, address indexed operator, address indexed owner); - -} \ No newline at end of file diff --git a/eip/eip-1410.md b/eip/eip-1410.md index 3b13027..8610220 100644 --- a/eip/eip-1410.md +++ b/eip/eip-1410.md @@ -290,8 +290,8 @@ interface IERC1410 { event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); // Issuance / Redemption Events - event IssuedByPartition(bytes32 indexed partition, address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); - event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 amount, bytes operatorData); + event IssuedByPartition(bytes32 indexed partition, address indexed operator, address indexed to, uint256 value, bytes data, bytes operatorData); + event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 value, bytes operatorData); } ``` From 6349fb1633645a6fc12e5197ade94786d9701d2c Mon Sep 17 00:00:00 2001 From: satyam Date: Mon, 7 Jan 2019 17:12:42 +0530 Subject: [PATCH 4/7] minor fixes in the basic class --- contracts/ERC1410/ERC1410Basic.sol | 7 ++- contracts/ERC1410/ERC1410Standard.sol | 2 +- test/erc1410.js | 79 +++++++++++++++++++++++++++ test/partial_fungible_test.js | 1 - 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 test/erc1410.js delete mode 100644 test/partial_fungible_test.js diff --git a/contracts/ERC1410/ERC1410Basic.sol b/contracts/ERC1410/ERC1410Basic.sol index 9b69759..9b82c1e 100644 --- a/contracts/ERC1410/ERC1410Basic.sol +++ b/contracts/ERC1410/ERC1410Basic.sol @@ -46,7 +46,7 @@ contract ERC1410Basic { /// @param _tokenHolder An address for whom to query the balance /// @return The number of tokens owned by `_tokenHolder`, possibly zero function balanceOf(address _tokenHolder) external view returns (uint256) { - balances[_tokenHolder]; + return balances[_tokenHolder]; } /// @notice Counts the balance associated with a specific partition assigned to an tokenHolder @@ -55,7 +55,7 @@ contract ERC1410Basic { /// @return The number of tokens owned by `_tokenHolder` with the metadata associated with `_partition`, possibly zero function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256) { if (_validPartition(_partition, _tokenHolder)) - return partitions[_tokenHolder][partitionToIndex[_tokenHolder][_partition]].amount; + return partitions[_tokenHolder][partitionToIndex[_tokenHolder][_partition] - 1].amount; else return 0; } @@ -81,7 +81,8 @@ contract ERC1410Basic { // Add a function to verify the `_data` parameter // TODO: Need to create the bytes division of the `_partition` so it can be easily findout in which receiver's partition // token will transfered. For current implementation we are assuming that the receiver's partition will be same as sender's - // as well as it also pass the `_validPartition()` check. + // as well as it also pass the `_validPartition()` check. In this particular case we are also assuming that reciever has the + // some tokens of the same partition as well (To avoid the array index out of bound error). // Note- There is no operator used for the execution of this call so `_operator` value in // in event is address(0) same for the `_operatorData` _transferByPartition(msg.sender, _to, _value, _partition, _data, address(0), "0x0"); diff --git a/contracts/ERC1410/ERC1410Standard.sol b/contracts/ERC1410/ERC1410Standard.sol index 81fe1cc..2041aaa 100644 --- a/contracts/ERC1410/ERC1410Standard.sol +++ b/contracts/ERC1410/ERC1410Standard.sol @@ -64,7 +64,7 @@ contract ERC1410Standard is IERC1410, ERC1410Operator, Ownable { emit RedeemedByPartition(_partition, _operator, _from, _value, _data, _operatorData); } - function _validateParams(bytes32 _partition, uint256 _value) internal { + function _validateParams(bytes32 _partition, uint256 _value) internal pure { require(_value != uint256(0), "Zero value not allowed"); require(_partition != bytes32(0), "Invalid partition"); } diff --git a/test/erc1410.js b/test/erc1410.js new file mode 100644 index 0000000..38ffe3c --- /dev/null +++ b/test/erc1410.js @@ -0,0 +1,79 @@ +import { catchRevert } from "./helpers/exceptions"; +import { takeSnapshot, revertToSnapshot } from "./helpers/time"; + +const ERC1410Token = artifacts.require('./ERC1410Standard.sol'); + +const Web3 = require("web3"); +const BigNumber = require("bignumber.js"); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port + +contract("ERC1410", accounts => { + +let tokenOwner; +let controller; +let tokenHolder1; +let tokenHolder2; +let erc1410Token; +const partition1 = "Debt"; +const partition2 = "Equity"; + +const empty_data = "0x0000000000000000000000000000000000000000"; +const zero_address = "0x0000000000000000000000000000000000000000"; + + before(async () => { + tokenHolder1 = accounts[3]; + tokenHolder2 = accounts[2]; + controller = accounts[5]; + tokenOwner = accounts[4]; + + erc1410Token = await ERC1410Token.new({from: tokenOwner}); + }); + + describe(`Test cases for the ERC1410 contract\n`, async () => { + + describe(`Test cases for the issuance/Minting`, async() => { + + it("\t Should issue the tokens by the partition\n", async() => { + await erc1410Token.issueByPartition(partition1, tokenHolder1, web3.utils.toWei("10"), "0x0", {from: tokenOwner}); + + assert.equal(web3.utils.fromWei((await erc1410Token.totalSupply.call()).toString()), 10); + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder1)).toString()), 10); + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition1, tokenHolder1)).toString()), + 10 + ); + assert.equal( + (await erc1410Token.partitionsOf(tokenHolder1)).length, 1 + ); + assert.equal( + web3.utils.toUtf8((await erc1410Token.partitionsOf(tokenHolder1))[0]), + partition1 + ); + }); + + it("\t Should failed to issue tokens by partition because of unauthorised msg.sender \n", async() => { + await catchRevert( + erc1410Token.issueByPartition(partition1, tokenHolder1, web3.utils.toWei("10"), "0x0", {from: controller}) + ); + }); + + it("\t Should issue some more tokens to another token holder of the same partition", async() => { + await erc1410Token.issueByPartition(partition1, tokenHolder2, web3.utils.toWei("50"), "0x0", {from: tokenOwner}); + + assert.equal(web3.utils.fromWei((await erc1410Token.totalSupply.call()).toString()), 60); + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder2)).toString()), 50); + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition1, tokenHolder2)).toString()), + 50 + ); + assert.equal( + (await erc1410Token.partitionsOf(tokenHolder2)).length, 1 + ); + assert.equal( + web3.utils.toUtf8((await erc1410Token.partitionsOf(tokenHolder2))[0]), + partition1 + ); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/partial_fungible_test.js b/test/partial_fungible_test.js deleted file mode 100644 index f3b6f71..0000000 --- a/test/partial_fungible_test.js +++ /dev/null @@ -1 +0,0 @@ -// Test case for the PartialFungibleToken standard \ No newline at end of file From d9fbea9f33e89e0d3f36d4099a824e8961714cda Mon Sep 17 00:00:00 2001 From: satyam Date: Tue, 8 Jan 2019 19:14:43 +0530 Subject: [PATCH 5/7] test cases for the partition --- contracts/ERC1410/ERC1410Basic.sol | 16 +++---- contracts/ERC1410/ERC1410Standard.sol | 2 + test/erc1410.js | 65 ++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/contracts/ERC1410/ERC1410Basic.sol b/contracts/ERC1410/ERC1410Basic.sol index 9b82c1e..ee1cd4d 100644 --- a/contracts/ERC1410/ERC1410Basic.sol +++ b/contracts/ERC1410/ERC1410Basic.sol @@ -101,13 +101,13 @@ contract ERC1410Basic { function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32) { // TODO: Applied the check over the `_data` parameter if (!_validPartition(_partition, _from)) - return (0x50, "Partition not exists", "0x0"); + return (0x50, "Partition not exists", bytes32("")); else if (partitions[_from][partitionToIndex[_from][_partition]].amount < _value) - return (0x52, "Insufficent balance", "0x0"); + return (0x52, "Insufficent balance", bytes32("")); else if (_to == address(0)) - return (0x57, "Invalid receiver", "0x0"); + return (0x57, "Invalid receiver", bytes32("")); else if (!KindMath.checkSub(balances[_from], _value) || !KindMath.checkAdd(balances[_to], _value)) - return (0x50, "Overflow", "0x0"); + 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); @@ -115,11 +115,11 @@ contract ERC1410Basic { function _transferByPartition(address _from, address _to, uint256 _value, bytes32 _partition, bytes _data, address _operator, bytes _operatorData) internal { require(_validPartition(_partition, _from), "Invalid partition"); - require(partitions[_from][partitionToIndex[_from][_partition]].amount >= _value, "Insufficient balance"); + require(partitions[_from][partitionToIndex[_from][_partition] - 1].amount >= _value, "Insufficient balance"); require(_to != address(0), "0x address not allowed"); - - uint256 _fromIndex = partitionToIndex[_from][_partition]; - uint256 _toIndex = partitionToIndex[_to][_partition]; + uint256 _fromIndex = partitionToIndex[_from][_partition] - 1; + 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); diff --git a/contracts/ERC1410/ERC1410Standard.sol b/contracts/ERC1410/ERC1410Standard.sol index 2041aaa..00c0bbd 100644 --- a/contracts/ERC1410/ERC1410Standard.sol +++ b/contracts/ERC1410/ERC1410Standard.sol @@ -19,6 +19,8 @@ contract ERC1410Standard is IERC1410, ERC1410Operator, Ownable { if (index == 0) { partitions[_tokenHolder].push(Partition(_value, _partition)); partitionToIndex[_tokenHolder][_partition] = partitions[_tokenHolder].length; + } else { + partitions[_tokenHolder][index - 1].amount = partitions[_tokenHolder][index - 1].amount.add(_value); } _totalSupply = _totalSupply.add(_value); balances[_tokenHolder] = balances[_tokenHolder].add(_value); diff --git a/test/erc1410.js b/test/erc1410.js index 38ffe3c..fb8b82b 100644 --- a/test/erc1410.js +++ b/test/erc1410.js @@ -51,16 +51,30 @@ const zero_address = "0x0000000000000000000000000000000000000000"; ); }); - it("\t Should failed to issue tokens by partition because of unauthorised msg.sender \n", async() => { - await catchRevert( - erc1410Token.issueByPartition(partition1, tokenHolder1, web3.utils.toWei("10"), "0x0", {from: controller}) + it("\t Should issue more tokens to the same token holder \n", async() => { + await erc1410Token.issueByPartition(partition1, tokenHolder1, web3.utils.toWei("20"), "0x0", {from: tokenOwner}); + + assert.equal(web3.utils.fromWei((await erc1410Token.totalSupply.call()).toString()), 30); + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder1)).toString()), 30); + + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition1, tokenHolder1)).toString()), + 30 + ); + assert.equal( + (await erc1410Token.partitionsOf(tokenHolder1)).length, 1 + ); + assert.equal( + web3.utils.toUtf8((await erc1410Token.partitionsOf(tokenHolder1))[0]), + partition1 ); }); - it("\t Should issue some more tokens to another token holder of the same partition", async() => { + + it("\t Should issue some more tokens to another token holder of the same partition \n", async() => { await erc1410Token.issueByPartition(partition1, tokenHolder2, web3.utils.toWei("50"), "0x0", {from: tokenOwner}); - assert.equal(web3.utils.fromWei((await erc1410Token.totalSupply.call()).toString()), 60); + assert.equal(web3.utils.fromWei((await erc1410Token.totalSupply.call()).toString()), 80); assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder2)).toString()), 50); assert.equal( web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition1, tokenHolder2)).toString()), @@ -74,6 +88,47 @@ const zero_address = "0x0000000000000000000000000000000000000000"; partition1 ); }); + + it("\t Should failed to issue tokens by partition because of unauthorised msg.sender \n", async() => { + await catchRevert( + erc1410Token.issueByPartition(partition1, tokenHolder1, web3.utils.toWei("10"), "0x0", {from: controller}) + ); + }); + + it("\t Should failed to issue tokens because of invalid partition \n", async() => { + await catchRevert( + erc1410Token.issueByPartition("0x0", tokenHolder1, web3.utils.toWei("10"), "0x0", {from: tokenOwner}) + ); + }); + + it("\t Should failed to issue tokens because reciever address is 0x \n", async() => { + await catchRevert( + erc1410Token.issueByPartition(partition1, zero_address, web3.utils.toWei("10"), "0x0", {from: tokenOwner}) + ); + }); + + it("\t Should failed to issue tokens because value is 0 \n", async() => { + await catchRevert( + erc1410Token.issueByPartition(partition1, tokenHolder2, 0, "0x0", {from: tokenOwner}) + ); + }); }); + + describe("Transfer the tokens (transferByPartition)", async() => { + + it("Transfer the tokens from token holder 1 to token holder 2", async() => { + let tx = await erc1410Token.transferByPartition(partition1, tokenHolder1, web3.utils.toWei("5"), "", {from: tokenHolder2}); + + // verify the event + assert.equal(web3.utils.toUtf8(tx.logs[0].args._fromPartition), partition1); + assert.equal(tx.logs[0].args._operator, zero_address); + assert.equal(tx.logs[0].args._from, tokenHolder2); + assert.equal(tx.logs[0].args._to, tokenHolder1); + assert.equal(web3.utils.fromWei((tx.logs[0].args._value).toString()), 5); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._data), ""); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._operatorData), "0x0"); + }); + + }) }); }); \ No newline at end of file From c02021768722eb475cb7d2b8f75c24ac416a97f2 Mon Sep 17 00:00:00 2001 From: satyam Date: Tue, 8 Jan 2019 20:04:26 +0530 Subject: [PATCH 6/7] Thank you @MichalZalecki for pointing out the removing document error. more detailed in #17 --- contracts/ERC1410/ERC1410Basic.sol | 2 +- contracts/ERC1410/ERC1410Standard.sol | 2 +- contracts/ERC1643/ERC1643.sol | 2 +- test/erc1410.js | 3 +- test/erc1643.js | 236 ++++++++++++++------------ 5 files changed, 127 insertions(+), 118 deletions(-) diff --git a/contracts/ERC1410/ERC1410Basic.sol b/contracts/ERC1410/ERC1410Basic.sol index ee1cd4d..f6919f6 100644 --- a/contracts/ERC1410/ERC1410Basic.sol +++ b/contracts/ERC1410/ERC1410Basic.sol @@ -85,7 +85,7 @@ contract ERC1410Basic { // some tokens of the same partition as well (To avoid the array index out of bound error). // Note- There is no operator used for the execution of this call so `_operator` value in // in event is address(0) same for the `_operatorData` - _transferByPartition(msg.sender, _to, _value, _partition, _data, address(0), "0x0"); + _transferByPartition(msg.sender, _to, _value, _partition, _data, address(0), ""); } /// @notice The standard provides an on-chain function to determine whether a transfer will succeed, diff --git a/contracts/ERC1410/ERC1410Standard.sol b/contracts/ERC1410/ERC1410Standard.sol index 00c0bbd..796c297 100644 --- a/contracts/ERC1410/ERC1410Standard.sol +++ b/contracts/ERC1410/ERC1410Standard.sol @@ -33,7 +33,7 @@ contract ERC1410Standard is IERC1410, ERC1410Operator, Ownable { /// @param _data Additional data attached to the burning of tokens function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external { // Add the function to validate the `_data` parameter - _redeemByPartition(_partition, msg.sender, address(0), _value, _data, "0x0"); + _redeemByPartition(_partition, msg.sender, address(0), _value, _data, ""); } /// @notice Decreases totalSupply and the corresponding amount of the specified partition of tokenHolder diff --git a/contracts/ERC1643/ERC1643.sol b/contracts/ERC1643/ERC1643.sol index 5fd5ba3..97a3037 100644 --- a/contracts/ERC1643/ERC1643.sol +++ b/contracts/ERC1643/ERC1643.sol @@ -54,7 +54,7 @@ contract ERC1643 is IERC1643, Ownable { uint256 index = _docIndexes[_name] - 1; if (index != _docNames.length - 1) { _docNames[index] = _docNames[_docNames.length - 1]; - _docIndexes[_name] = index + 1; + _docIndexes[_docNames[index]] = index + 1; } _docNames.length--; emit DocumentRemoved(_name, _documents[_name].uri, _documents[_name].docHash); diff --git a/test/erc1410.js b/test/erc1410.js index fb8b82b..dc1bee6 100644 --- a/test/erc1410.js +++ b/test/erc1410.js @@ -126,9 +126,8 @@ const zero_address = "0x0000000000000000000000000000000000000000"; assert.equal(tx.logs[0].args._to, tokenHolder1); assert.equal(web3.utils.fromWei((tx.logs[0].args._value).toString()), 5); assert.equal(web3.utils.toUtf8(tx.logs[0].args._data), ""); - assert.equal(web3.utils.toUtf8(tx.logs[0].args._operatorData), "0x0"); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._operatorData), ""); }); - }) }); }); \ No newline at end of file diff --git a/test/erc1643.js b/test/erc1643.js index 59466e3..7c31037 100644 --- a/test/erc1643.js +++ b/test/erc1643.js @@ -19,7 +19,7 @@ const docHash = "hello"; const empty_hash = "0x0000000000000000000000000000000000000000000000000000000000000000"; async function latestTime() { - return (await web3.eth.getBlock("latest")).timestamp; + return (await web3.eth.getBlock("latest")).timestamp; } before(async () => { @@ -33,113 +33,121 @@ const empty_hash = "0x0000000000000000000000000000000000000000000000000000000000 describe(`Test cases for the ERC1643 contract\n`, async () => { - describe(`Test cases for the setDocument() function of the ERC1643\n`, async() => { - - it("\tShould failed in executing the setDocument() function because msg.sender is not authorised\n", async() => { - await catchRevert( - erc1643.setDocument("doc1", "https://www.gogl.bts.fly", "0x0", {from: account1}) - ); - }); - - it("\tShould failed to set a document details as name is empty\n", async() => { - await catchRevert( - erc1643.setDocument("", "https://www.gogl.bts.fly", "0x0", {from: tokenOwner}) - ); - }); - - it("\tShould failed to set a document details as URI is empty\n", async() => { - await catchRevert( - erc1643.setDocument("doc1", "", "0x0", {from: tokenOwner}) - ); - }); - - it("\tShould sucessfully add the document details in the `_documents` mapping and change the length of the `_docsNames`\n", async() => { - let tx = await erc1643.setDocument("doc1", uri, docHash, {from: tokenOwner}); - assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc1"); - assert.equal(tx.logs[0].args._uri, uri); - assert.equal(web3.utils.toUtf8(tx.logs[0].args._documentHash), docHash); - assert.equal((await erc1643.getAllDocuments.call()).length, 1); - }); - - it("\tShould successfully add the new document and allow the empty docHash to be added in the `Document` structure\n", async() => { - let tx = await erc1643.setDocument("doc2", uri, "0x0", {from: tokenOwner}); - assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc2"); - assert.equal(tx.logs[0].args._uri, uri); - assert.equal(tx.logs[0].args._documentHash, empty_hash); - assert.equal((await erc1643.getAllDocuments.call()).length, 2); - }); - - it("\tShould successfully update the existing document and length of `_docsNames` should remain unaffected\n", async() => { - let tx = await erc1643.setDocument("doc2", "https://www.bts.l", "0x0", {from: tokenOwner}); - assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc2"); - assert.equal(tx.logs[0].args._uri, "https://www.bts.l"); - assert.equal(tx.logs[0].args._documentHash, empty_hash); - assert.equal((await erc1643.getAllDocuments.call()).length, 2); - }); - - describe("Test cases for the getters functions\n", async()=> { - - it("\tShould get the details of existed document\n", async() => { - let doc1Details = await erc1643.getDocument.call("doc1"); - assert.equal(doc1Details[0], uri); - assert.equal(web3.utils.toUtf8(doc1Details[1]), docHash); - assert.closeTo(doc1Details[2].toNumber(), await latestTime(), 2); - - let doc2Details = await erc1643.getDocument.call("doc2"); - assert.equal(doc2Details[0], "https://www.bts.l"); - assert.equal(doc2Details[1], empty_hash); - assert.closeTo(doc2Details[2].toNumber(), await latestTime(), 2); - }); - - it("\tShould get the details of the non-existed document it means every value should be zero\n", async() => { - let doc3Details = await erc1643.getDocument.call("doc3"); - assert.equal(doc3Details[0], ""); - assert.equal(web3.utils.toUtf8(doc3Details[1]), ""); - assert.equal(doc3Details[2], 0); - }); - - it("\tShould get all the documents present in the contract\n", async() => { - let allDocs = await erc1643.getAllDocuments.call() - assert.equal(allDocs.length, 2); - assert.equal(web3.utils.toUtf8(allDocs[0]), "doc1"); - assert.equal(web3.utils.toUtf8(allDocs[1]), "doc2"); - }); - }) - }); - - describe("Test cases for the removeDocument()\n", async() => { - - it("\tShould failed to remove document because msg.sender is not authorised\n", async() => { - await catchRevert( - erc1643.removeDocument("doc2", {from: account1}) - ); - }); - - it("\tShould failed to remove the document that is not existed in the contract\n", async() => { - await catchRevert( - erc1643.removeDocument("doc3", {from: tokenOwner}) - ); - }); - - it("\tShould succssfully remove the document from the contract which is present in the last index of the `_docsName` and check the params of the `DocumentRemoved` event\n", async() => { - // first add the new document - await erc1643.setDocument("doc3", "https://www.bts.l", "0x0", {from: tokenOwner}); - // as this will be last in the array so remove this - let tx = await erc1643.removeDocument("doc3", {from: tokenOwner}); - assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc3"); - assert.equal(tx.logs[0].args._uri, "https://www.bts.l"); - assert.equal(tx.logs[0].args._documentHash, empty_hash); - assert.equal((await erc1643.getAllDocuments.call()).length, 2); - - // remove the document that is not last in the `docsName` array - tx = await erc1643.removeDocument("doc1", {from: tokenOwner}); - assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc1"); - assert.equal(tx.logs[0].args._uri, uri); - assert.equal(web3.utils.toUtf8(tx.logs[0].args._documentHash), docHash); - assert.equal((await erc1643.getAllDocuments.call()).length, 1); - }); - - describe("Test cases for the getters functions\n", async()=> { + describe(`Test cases for the setDocument() function of the ERC1643\n`, async() => { + + it("\tShould failed in executing the setDocument() function because msg.sender is not authorised\n", async() => { + await catchRevert( + erc1643.setDocument("doc1", "https://www.gogl.bts.fly", "0x0", {from: account1}) + ); + }); + + it("\tShould failed to set a document details as name is empty\n", async() => { + await catchRevert( + erc1643.setDocument("", "https://www.gogl.bts.fly", "0x0", {from: tokenOwner}) + ); + }); + + it("\tShould failed to set a document details as URI is empty\n", async() => { + await catchRevert( + erc1643.setDocument("doc1", "", "0x0", {from: tokenOwner}) + ); + }); + + it("\tShould sucessfully add the document details in the `_documents` mapping and change the length of the `_docsNames`\n", async() => { + let tx = await erc1643.setDocument("doc1", uri, docHash, {from: tokenOwner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc1"); + assert.equal(tx.logs[0].args._uri, uri); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._documentHash), docHash); + assert.equal((await erc1643.getAllDocuments.call()).length, 1); + }); + + it("\tShould successfully add the new document and allow the empty docHash to be added in the `Document` structure\n", async() => { + let tx = await erc1643.setDocument("doc2", uri, "0x0", {from: tokenOwner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc2"); + assert.equal(tx.logs[0].args._uri, uri); + assert.equal(tx.logs[0].args._documentHash, empty_hash); + assert.equal((await erc1643.getAllDocuments.call()).length, 2); + }); + + it("\tShould successfully update the existing document and length of `_docsNames` should remain unaffected\n", async() => { + let tx = await erc1643.setDocument("doc2", "https://www.bts.l", "0x0", {from: tokenOwner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc2"); + assert.equal(tx.logs[0].args._uri, "https://www.bts.l"); + assert.equal(tx.logs[0].args._documentHash, empty_hash); + assert.equal((await erc1643.getAllDocuments.call()).length, 2); + }); + + describe("Test cases for the getters functions\n", async()=> { + + it("\tShould get the details of existed document\n", async() => { + let doc1Details = await erc1643.getDocument.call("doc1"); + assert.equal(doc1Details[0], uri); + assert.equal(web3.utils.toUtf8(doc1Details[1]), docHash); + assert.closeTo(doc1Details[2].toNumber(), await latestTime(), 2); + + let doc2Details = await erc1643.getDocument.call("doc2"); + assert.equal(doc2Details[0], "https://www.bts.l"); + assert.equal(doc2Details[1], empty_hash); + assert.closeTo(doc2Details[2].toNumber(), await latestTime(), 2); + }); + + it("\tShould get the details of the non-existed document it means every value should be zero\n", async() => { + let doc3Details = await erc1643.getDocument.call("doc3"); + assert.equal(doc3Details[0], ""); + assert.equal(web3.utils.toUtf8(doc3Details[1]), ""); + assert.equal(doc3Details[2], 0); + }); + + it("\tShould get all the documents present in the contract\n", async() => { + let allDocs = await erc1643.getAllDocuments.call() + assert.equal(allDocs.length, 2); + assert.equal(web3.utils.toUtf8(allDocs[0]), "doc1"); + assert.equal(web3.utils.toUtf8(allDocs[1]), "doc2"); + }); + }) + }); + + describe("Test cases for the removeDocument()\n", async() => { + + it("\tShould failed to remove document because msg.sender is not authorised\n", async() => { + await catchRevert( + erc1643.removeDocument("doc2", {from: account1}) + ); + }); + + it("\tShould failed to remove the document that is not existed in the contract\n", async() => { + await catchRevert( + erc1643.removeDocument("doc3", {from: tokenOwner}) + ); + }); + + it("\tShould succssfully remove the document from the contract which is present in the last index of the `_docsName` and check the params of the `DocumentRemoved` event\n", async() => { + // first add the new document + await erc1643.setDocument("doc3", "https://www.bts.l", "0x0", {from: tokenOwner}); + // as this will be last in the array so remove this + let tx = await erc1643.removeDocument("doc3", {from: tokenOwner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc3"); + assert.equal(tx.logs[0].args._uri, "https://www.bts.l"); + assert.equal(tx.logs[0].args._documentHash, empty_hash); + assert.equal((await erc1643.getAllDocuments.call()).length, 2); + + // remove the document that is not last in the `docsName` array + tx = await erc1643.removeDocument("doc1", {from: tokenOwner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc1"); + assert.equal(tx.logs[0].args._uri, uri); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._documentHash), docHash); + assert.equal((await erc1643.getAllDocuments.call()).length, 1); + }); + + it("\t Should delete the doc to validate the #17 issue problem", async() => { + let tx = await erc1643.removeDocument("doc2", {from: tokenOwner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._name), "doc2"); + assert.equal(tx.logs[0].args._uri, "https://www.bts.l"); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._documentHash), ''); + assert.equal((await erc1643.getAllDocuments.call()).length, 0); + }); + + describe("Test cases for the getters functions\n", async()=> { it("\tShould get the details of the non-existed (earlier was present but get removed ) document it means every value should be zero\n", async() => { let doc1Details = await erc1643.getDocument.call("doc1"); @@ -149,11 +157,13 @@ const empty_hash = "0x0000000000000000000000000000000000000000000000000000000000 }); it("\tShould get all the documents present in the contract which should be 1\n", async() => { + // add one doc before the getter call + await erc1643.setDocument("doc4", "https://www.bts.l", docHash, {from: tokenOwner}) let allDocs = await erc1643.getAllDocuments.call() assert.equal(allDocs.length, 1); - assert.equal(web3.utils.toUtf8(allDocs[0]), "doc2"); + assert.equal(web3.utils.toUtf8(allDocs[0]), "doc4"); }); - }); - }) - }); - }); \ No newline at end of file + }); + }) +}); +}); \ No newline at end of file From aa985b1803441aee8a9a848da95030b0eff0096a Mon Sep 17 00:00:00 2001 From: satyam Date: Wed, 9 Jan 2019 18:13:38 +0530 Subject: [PATCH 7/7] final test suite --- contracts/ERC1410/ERC1410Standard.sol | 15 +- test/erc1410.js | 319 +++++++++++++++++++++++++- 2 files changed, 328 insertions(+), 6 deletions(-) diff --git a/contracts/ERC1410/ERC1410Standard.sol b/contracts/ERC1410/ERC1410Standard.sol index 796c297..07eb3a8 100644 --- a/contracts/ERC1410/ERC1410Standard.sol +++ b/contracts/ERC1410/ERC1410Standard.sol @@ -60,12 +60,25 @@ contract ERC1410Standard is IERC1410, ERC1410Operator, Ownable { require(_validPartition(_partition, _from), "Invalid partition"); uint256 index = partitionToIndex[_from][_partition] - 1; require(partitions[_from][index].amount >= _value, "Insufficient value"); - partitions[_from][index].amount = partitions[_from][index].amount.sub(_value); + if (partitions[_from][index].amount == _value) { + _deletePartitionForHolder(_from, _partition, index); + } else { + partitions[_from][index].amount = partitions[_from][index].amount.sub(_value); + } balances[_from] = balances[_from].sub(_value); _totalSupply = _totalSupply.sub(_value); emit RedeemedByPartition(_partition, _operator, _from, _value, _data, _operatorData); } + function _deletePartitionForHolder(address _holder, bytes32 _partition, uint256 index) internal { + if (index != partitions[_holder].length -1) { + partitions[_holder][index] = partitions[_holder][partitions[_holder].length -1]; + partitionToIndex[_holder][partitions[_holder][index].partition] = index + 1; + } + delete partitionToIndex[_holder][_partition]; + partitions[_holder].length--; + } + function _validateParams(bytes32 _partition, uint256 _value) internal pure { require(_value != uint256(0), "Zero value not allowed"); require(_partition != bytes32(0), "Invalid partition"); diff --git a/test/erc1410.js b/test/erc1410.js index dc1bee6..d55a5bf 100644 --- a/test/erc1410.js +++ b/test/erc1410.js @@ -1,4 +1,4 @@ -import { catchRevert } from "./helpers/exceptions"; +import { catchRevert, catchInvalidOpcode } from "./helpers/exceptions"; import { takeSnapshot, revertToSnapshot } from "./helpers/time"; const ERC1410Token = artifacts.require('./ERC1410Standard.sol'); @@ -10,12 +10,14 @@ const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); contract("ERC1410", accounts => { let tokenOwner; -let controller; +let operator1; +let operator2; let tokenHolder1; let tokenHolder2; let erc1410Token; const partition1 = "Debt"; const partition2 = "Equity"; +const partition3 = "locked"; const empty_data = "0x0000000000000000000000000000000000000000"; const zero_address = "0x0000000000000000000000000000000000000000"; @@ -23,7 +25,8 @@ const zero_address = "0x0000000000000000000000000000000000000000"; before(async () => { tokenHolder1 = accounts[3]; tokenHolder2 = accounts[2]; - controller = accounts[5]; + operator1 = accounts[5]; + operator2 = accounts[6]; tokenOwner = accounts[4]; erc1410Token = await ERC1410Token.new({from: tokenOwner}); @@ -91,7 +94,7 @@ const zero_address = "0x0000000000000000000000000000000000000000"; it("\t Should failed to issue tokens by partition because of unauthorised msg.sender \n", async() => { await catchRevert( - erc1410Token.issueByPartition(partition1, tokenHolder1, web3.utils.toWei("10"), "0x0", {from: controller}) + erc1410Token.issueByPartition(partition1, tokenHolder1, web3.utils.toWei("10"), "0x0", {from: operator1}) ); }); @@ -116,7 +119,7 @@ const zero_address = "0x0000000000000000000000000000000000000000"; describe("Transfer the tokens (transferByPartition)", async() => { - it("Transfer the tokens from token holder 1 to token holder 2", async() => { + it("\t Should transfer the tokens from token holder 1 to token holder 2 \n", async() => { let tx = await erc1410Token.transferByPartition(partition1, tokenHolder1, web3.utils.toWei("5"), "", {from: tokenHolder2}); // verify the event @@ -128,6 +131,312 @@ const zero_address = "0x0000000000000000000000000000000000000000"; assert.equal(web3.utils.toUtf8(tx.logs[0].args._data), ""); assert.equal(web3.utils.toUtf8(tx.logs[0].args._operatorData), ""); }); + + it("\t Should transfer the tokens from token holder 2 to token holder 1 \n", async() => { + let tx = await erc1410Token.transferByPartition(partition1, tokenHolder2, web3.utils.toWei("10"), "", {from: tokenHolder1}); + + // verify the event + assert.equal(web3.utils.toUtf8(tx.logs[0].args._fromPartition), partition1); + assert.equal(tx.logs[0].args._operator, zero_address); + assert.equal(tx.logs[0].args._from, tokenHolder1); + assert.equal(tx.logs[0].args._to, tokenHolder2); + assert.equal(web3.utils.fromWei((tx.logs[0].args._value).toString()), 10); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._data), ""); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._operatorData), ""); + + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder2)).toString()), 55); + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition1, tokenHolder2)).toString()), + 55 + ); + }); + + it("\t Should fail to transfer the tokens from a invalid partition\n", async() => { + await catchRevert( + erc1410Token.transferByPartition("locked", tokenHolder2, web3.utils.toWei("10"), "", {from: tokenHolder1}) + ); + }); + + it("\t Should fail to transfer when partition balance is insufficient\n", async() => { + await catchRevert( + erc1410Token.transferByPartition(partition1, tokenHolder2, web3.utils.toWei("30"), "", {from: tokenHolder1}) + ); + }); + + it("\t Should fail to transfer when reciever address is 0x\n", async() => { + await catchRevert( + erc1410Token.transferByPartition(partition1, zero_address, web3.utils.toWei("10"), "", {from: tokenHolder1}) + ); + }); + + it("\t Should issue more tokens of different partitions to token holder 1 & 2\n", async() => { + await erc1410Token.issueByPartition(partition2, tokenHolder1, web3.utils.toWei("20"), "0x0", {from: tokenOwner}); + + assert.equal(web3.utils.fromWei((await erc1410Token.totalSupply.call()).toString()), 100); + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder1)).toString()), 45); + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition2, tokenHolder1)).toString()), + 20 + ); + assert.equal( + (await erc1410Token.partitionsOf(tokenHolder1)).length, 2 + ); + assert.equal( + web3.utils.toUtf8((await erc1410Token.partitionsOf(tokenHolder1))[1]), + partition2 + ); + + await erc1410Token.issueByPartition(partition3, tokenHolder2, web3.utils.toWei("30"), "0x0", {from: tokenOwner}); + + assert.equal(web3.utils.fromWei((await erc1410Token.totalSupply.call()).toString()), 130); + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder2)).toString()), 85); + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition3, tokenHolder2)).toString()), + 30 + ); + assert.equal( + (await erc1410Token.partitionsOf(tokenHolder2)).length, 2 + ); + assert.equal( + web3.utils.toUtf8((await erc1410Token.partitionsOf(tokenHolder2))[1]), + partition3 + ); + }); + + it("\t Should fail to transfer the tokens from a partition because reciever doesn't have the partition tokens\n", async() => { + await catchInvalidOpcode( + erc1410Token.transferByPartition(partition2, tokenHolder2, web3.utils.toWei("3"), "", {from: tokenHolder1}) + ); + }); + }); + + describe("Test cases for verifying the output of canTransferByPartition()", async() => { + + it("\t Should transfer the tokens from token holder 1 to token holder 2 \n", async() => { + let op = await erc1410Token.canTransferByPartition.call(tokenHolder2, tokenHolder1, partition1, web3.utils.toWei("5"), ""); + assert.equal(op[0], 0x51); + assert.equal(web3.utils.toUtf8(op[1]), "Success"); + assert.equal(web3.utils.toUtf8(op[2]), partition1); + }) + + it("\t Should transfer the tokens from token holder 2 to token holder 1 \n", async() => { + let op = await erc1410Token.canTransferByPartition.call(tokenHolder1, tokenHolder2, partition1, web3.utils.toWei("10"), ""); + assert.equal(op[0], 0x51); + assert.equal(web3.utils.toUtf8(op[1]), "Success"); + assert.equal(web3.utils.toUtf8(op[2]), partition1); + }) + + it("\t Should fail to transfer the tokens from a invalid partition\n", async() => { + let op = await erc1410Token.canTransferByPartition.call(tokenHolder1, tokenHolder2, "Vested", web3.utils.toWei("10"), ""); + assert.equal(op[0], 0x50); + assert.equal(web3.utils.toUtf8(op[1]), "Partition not exists"); + assert.equal(web3.utils.toUtf8(op[2]), ""); + }) + + it("\t Should fail to transfer when partition balance is insufficient\n", async() => { + let op = await erc1410Token.canTransferByPartition.call(tokenHolder1, tokenHolder2, partition1, web3.utils.toWei("30"), ""); + assert.equal(op[0], 0x52); + assert.equal(web3.utils.toUtf8(op[1]), "Insufficent balance"); + assert.equal(web3.utils.toUtf8(op[2]), ""); + }) + + it("\t Should fail to transfer when reciever address is 0x\n", async() => { + let op = await erc1410Token.canTransferByPartition.call(tokenHolder1, zero_address, partition1, web3.utils.toWei("10"), ""); + assert.equal(op[0], 0x57); + assert.equal(web3.utils.toUtf8(op[1]), "Invalid receiver"); + assert.equal(web3.utils.toUtf8(op[2]), ""); + }); + }); + + describe("Test cases for the Operator functionality", async() => { + + it("\t Should authorize the operator\n", async() => { + let tx = await erc1410Token.authorizeOperator(operator1, {from: tokenHolder1}); + assert.equal(tx.logs[0].args.operator, operator1); + assert.equal(tx.logs[0].args.tokenHolder, tokenHolder1); + }); + + it("\t Should check for the operator \n", async() => { + assert.isTrue(await erc1410Token.isOperator.call(operator1, tokenHolder1)); + }); + + it("\t Should return false by the isOperatorForPartition \n", async() => { + assert.isFalse(await erc1410Token.isOperatorForPartition.call(partition1, operator1, tokenHolder1)); + }); + + it(" \t Should transfer the tokens by OperatorByPartition\n", async() => { + let tx = await erc1410Token.operatorTransferByPartition( + partition1, tokenHolder1, tokenHolder2, web3.utils.toWei("2"), "", "Lawyer", {from: operator1} + ); + + // verify the event + assert.equal(web3.utils.toUtf8(tx.logs[0].args._fromPartition), partition1); + assert.equal(tx.logs[0].args._operator, operator1); + assert.equal(tx.logs[0].args._from, tokenHolder1); + assert.equal(tx.logs[0].args._to, tokenHolder2); + assert.equal(web3.utils.fromWei((tx.logs[0].args._value).toString()), 2); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._data), ""); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._operatorData), "Lawyer"); + + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder2)).toString()), 87); + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition1, tokenHolder2)).toString()), + 57 + ); + }); + + it("\t Should fail to transfer the tokens by OperatorByPartition because of unauthorised operator\n", async() => { + await catchRevert( + erc1410Token.operatorTransferByPartition( + partition1, tokenHolder1, tokenHolder2, web3.utils.toWei("2"), "", "Lawyer", {from: operator2} + ) + ); + }); + + it("\t Should revoke the operator\n", async() => { + let tx = await erc1410Token.revokeOperator(operator1, {from: tokenHolder1}); + assert.equal(tx.logs[0].args.operator, operator1); + assert.equal(tx.logs[0].args.tokenHolder, tokenHolder1); + }); + + it("\t Should succesfully authorize the operator by partition\n", async() => { + let tx = await erc1410Token.authorizeOperatorByPartition(partition1, operator2, {from: tokenHolder1}); + assert.equal(tx.logs[0].args.operator, operator2); + assert.equal(web3.utils.toUtf8(tx.logs[0].args.partition), partition1); + assert.equal(tx.logs[0].args.tokenHolder, tokenHolder1); + }); + + it("\t Should give true by isOperatorForPartition\n", async() => { + assert.isTrue(await erc1410Token.isOperatorForPartition.call(partition1, operator2, tokenHolder1)); + }); + + it("\t Should transfer the tokens usng operator\n", async() => { + let tx = await erc1410Token.operatorTransferByPartition( + partition1, tokenHolder1, tokenHolder2, web3.utils.toWei("2"), "", "Lawyer", {from: operator2} + ); + + // verify the event + assert.equal(web3.utils.toUtf8(tx.logs[0].args._fromPartition), partition1); + assert.equal(tx.logs[0].args._operator, operator2); + assert.equal(tx.logs[0].args._from, tokenHolder1); + assert.equal(tx.logs[0].args._to, tokenHolder2); + assert.equal(web3.utils.fromWei((tx.logs[0].args._value).toString()), 2); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._data), ""); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._operatorData), "Lawyer"); + + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder2)).toString()), 89); + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition1, tokenHolder2)).toString()), + 59 + ); + }); + + it("\t Should fail to transfer the token because operator is get revoked\n", async() => { + let tx = await erc1410Token.revokeOperatorByPartition(partition1, operator2, {from: tokenHolder1}); + assert.equal(tx.logs[0].args.operator, operator2); + assert.equal(web3.utils.toUtf8(tx.logs[0].args.partition), partition1); + assert.equal(tx.logs[0].args.tokenHolder, tokenHolder1); + }); + + it("\t Should fail to transfer the tokens by OperatorByPartition because of unauthorised operator\n", async() => { + await catchRevert( + erc1410Token.operatorTransferByPartition( + partition1, tokenHolder1, tokenHolder2, web3.utils.toWei("2"), "", "Lawyer", {from: operator2} + ) + ); + }); + }); + + describe("Test the redeem functionality", async() => { + + it("\t Should fail to redeem the tokens as the value is 0 \n", async() => { + await catchRevert( + erc1410Token.redeemByPartition(partition1, 0, "", {from: tokenHolder2}) + ); + }); + + it("\t Should fail to redeem the tokens as the partition is 0 \n", async() => { + await catchRevert( + erc1410Token.redeemByPartition(empty_data, 0, web3.utils.toWei("7"), {from: tokenHolder2}) + ); + }); + + it("\t Should fail to redeem the tokens as the partition is invalid\n", async() => { + await catchRevert( + erc1410Token.redeemByPartition(partition2, 0, web3.utils.toWei("7"), {from: tokenHolder2}) + ); + }); + + it("\t Should fail to redeem the tokens because holder doesn't have sufficeint balance\n", async() => { + await catchRevert( + erc1410Token.redeemByPartition(partition2, 0, web3.utils.toWei("70"), {from: tokenHolder2}) + ); + }); + + it("\t Should successfully redeem the tokens\n", async() => { + let tx = await erc1410Token.redeemByPartition(partition1, web3.utils.toWei("7"), "", {from: tokenHolder2}); + + // verify the event + assert.equal(web3.utils.toUtf8(tx.logs[0].args.partition), partition1); + assert.equal(tx.logs[0].args.operator, zero_address); + assert.equal(tx.logs[0].args.from, tokenHolder2); + assert.equal(web3.utils.fromWei((tx.logs[0].args.value).toString()), 7); + assert.equal(web3.utils.toUtf8(tx.logs[0].args.data), ""); + assert.equal(web3.utils.toUtf8(tx.logs[0].args.operatorData), ""); + + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder2)).toString()), 82); + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition1, tokenHolder2)).toString()), + 52 + ); + assert.equal(web3.utils.fromWei((await erc1410Token.totalSupply.call()).toString()), 123); + }); + + it("\t Should fail to redeem tokens by the operator because token holder is zero address\n", async() => { + await erc1410Token.authorizeOperatorByPartition(partition3, operator2, {from: tokenHolder2}); + let value = (await erc1410Token.balanceOfByPartition.call(partition3, tokenHolder2)).toString(); + await catchRevert( + erc1410Token.operatorRedeemByPartition( + partition3, zero_address, value, "", "illegal", {from: operator2} + ) + ); + }); + + it("\t Should fail to redeem to tokens by the operator because operator is invalid\n", async() => { + let value = (await erc1410Token.balanceOfByPartition.call(partition3, tokenHolder2)).toString(); + await catchRevert( + erc1410Token.operatorRedeemByPartition( + partition3, zero_address, value, "", "illegal", {from: operator1} + ) + ); + }) + + it("\t Should successfully redeem tokens by the operator \n", async() => { + + let value = await erc1410Token.balanceOfByPartition.call(partition3, tokenHolder2); + let tx = await erc1410Token.operatorRedeemByPartition( + partition3, tokenHolder2, value, "", "illegal", {from: operator2} + ); + + // verify the event + assert.equal(web3.utils.toUtf8(tx.logs[0].args.partition), partition3); + assert.equal(tx.logs[0].args.operator, operator2); + assert.equal(tx.logs[0].args.from, tokenHolder2); + assert.equal((tx.logs[0].args.value).toString(), value.toString()); + assert.equal(web3.utils.toUtf8(tx.logs[0].args.data), ""); + assert.equal(web3.utils.toUtf8(tx.logs[0].args.operatorData), "illegal"); + + assert.equal(web3.utils.fromWei((await erc1410Token.balanceOf.call(tokenHolder2)).toString()), 52); + assert.equal( + web3.utils.fromWei((await erc1410Token.balanceOfByPartition.call(partition3, tokenHolder2)).toString()), + 0 + ); + assert.equal( + (await erc1410Token.partitionsOf(tokenHolder2)).length, 1 + ); + assert.equal(web3.utils.fromWei((await erc1410Token.totalSupply.call()).toString()), 93); + }); + }) }); }); \ No newline at end of file