// SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.0; import "./interfaces/IERC3475.sol"; contract ERC3475 is IERC3475 { /** * @notice this Struct is representing the Nonce properties as an object * */ struct Nonce { // stores the values corresponding to the dates (issuance and maturity date). mapping(uint256 => IERC3475.Values) _values; // storing the issuance of the issued bonds with their balances. mapping(address => uint256) _balances; // defines the mapping for amount of bonds that can be delegated by Owner => operator address. mapping(address => mapping(address => uint256)) _allowances; // Overall supplies of this nonce uint256 _activeSupply; uint256 _burnedSupply; uint256 _redeemedSupply; } /** * @notice this Struct is representing the Class properties as an object * and can be retrieved by the classId */ struct Class { mapping(uint256 => IERC3475.Values) _values; mapping(uint256 => IERC3475.Metadata) _nonceMetadata; mapping(uint256 => Nonce) nonces; } mapping(address => mapping(address => bool)) operatorApprovals; // from classId given mapping(uint256 => Class) internal _classes; mapping(uint256 => IERC3475.Metadata) _classMetadata; /** * @notice Here the constructor is just to initialize a class and nonce, * in practice, you will have a function to create a new class and nonce * to be deployed during the initial deployment cycle */ constructor() { // define "symbol of the given class"; _classMetadata[0].title = "symbol"; _classMetadata[0]._type = "string"; _classMetadata[0].description = "symbol of the class"; // define "period of the class"; _classMetadata[5].title = "period"; _classMetadata[5]._type = "int"; _classMetadata[5].description = "value (in months) about maturity time"; // describing the symbol of the different class _classes[0]._values[0].stringValue = "DBIT Fix 6M"; _classes[1]._values[0].stringValue = "DBIT Fix test Instantaneous"; // define the maturity time period (for the test class). _classes[0]._values[5].uintValue = 10; _classes[1]._values[5].uintValue = 1; // write the time of maturity to nonce values, in other implementation, a create nonce function can be added _classes[0].nonces[0]._values[0].uintValue = block.timestamp + 180 days; _classes[0].nonces[1]._values[0].uintValue = block.timestamp + 181 days; _classes[0].nonces[2]._values[0].uintValue = block.timestamp + 182 days; // test for review the instantaneous class _classes[1].nonces[0]._values[0].uintValue = block.timestamp + 1; _classes[1].nonces[1]._values[0].uintValue = block.timestamp + 2; _classes[1].nonces[2]._values[0].uintValue = block.timestamp + 3; // define metadata explaining "maturity of the nonce"; _classes[0]._nonceMetadata[0].title = "maturity"; _classes[0]._nonceMetadata[0]._type = "int"; _classes[0]._nonceMetadata[0].description = "maturity date in integer"; _classes[1]._nonceMetadata[0].title = "maturity"; _classes[1]._nonceMetadata[0]._type = "int"; _classes[1]._nonceMetadata[0].description = "maturity date in integer"; // initializing all of the nonces for issued bonds _classes[0].nonces[0]._values[0].boolValue = true; _classes[0].nonces[1]._values[0].boolValue = true; _classes[0].nonces[2]._values[0].boolValue = true; } // Writable functions. function transferFrom( address _from, address _to, Transaction[] calldata _transactions ) public virtual override { require( _from != address(0), "ERC3475: can't transfer from the zero address" ); require( _to != address(0), "ERC3475:use burn() instead" ); require( msg.sender == _from || isApprovedFor(_from, msg.sender), "ERC3475:caller-not-owner-or-approved" ); uint256 len = _transactions.length; for (uint256 i = 0; i < len; i++) { _transferFrom(_from, _to, _transactions[i]); } emit Transfer(msg.sender, _from, _to, _transactions); } function transferAllowanceFrom( address _from, address _to, Transaction[] calldata _transactions ) public virtual override { require( _from != address(0), "ERC3475: can't transfer allowed amt from zero address" ); require( _to != address(0), "ERC3475: use burn() instead" ); uint256 len = _transactions.length; for (uint256 i = 0; i < len; i++) { require( _transactions[i]._amount <= allowance(_from, msg.sender, _transactions[i].classId, _transactions[i].nonceId), "ERC3475:caller-not-owner-or-approved" ); _transferAllowanceFrom(msg.sender, _from, _to, _transactions[i]); } emit Transfer(msg.sender, _from, _to, _transactions); } function issue(address _to, Transaction[] calldata _transactions) external virtual override { uint256 len = _transactions.length; for (uint256 i = 0; i < len; i++) { require( _to != address(0), "ERC3475: can't issue to the zero address" ); _issue(_to, _transactions[i]); } emit Issue(msg.sender, _to, _transactions); } function redeem(address _from, Transaction[] calldata _transactions) external virtual override { require( _from != address(0), "ERC3475: can't redeem from the zero address" ); uint256 len = _transactions.length; for (uint256 i = 0; i < len; i++) { (, uint256 progressRemaining) = getProgress( _transactions[i].classId, _transactions[i].nonceId ); require( progressRemaining == 0, "ERC3475 Error: Not redeemable" ); _redeem(_from, _transactions[i]); } emit Redeem(msg.sender, _from, _transactions); } function burn(address _from, Transaction[] calldata _transactions) external virtual override { require( _from != address(0), "ERC3475: can't burn from the zero address" ); require( msg.sender == _from || isApprovedFor(_from, msg.sender), "ERC3475: caller-not-owner-or-approved" ); uint256 len = _transactions.length; for (uint256 i = 0; i < len; i++) { _burn(_from, _transactions[i]); } emit Burn(msg.sender, _from, _transactions); } function approve(address _spender, Transaction[] calldata _transactions) external virtual override { for (uint256 i = 0; i < _transactions.length; i++) { _classes[_transactions[i].classId] .nonces[_transactions[i].nonceId] ._allowances[msg.sender][_spender] = _transactions[i]._amount; } } function setApprovalFor( address operator, bool approved ) public virtual override { operatorApprovals[msg.sender][operator] = approved; emit ApprovalFor(msg.sender, operator, approved); } // READABLES function totalSupply(uint256 classId, uint256 nonceId) public view override returns (uint256) { return (activeSupply(classId, nonceId) + burnedSupply(classId, nonceId) + redeemedSupply(classId, nonceId) ); } function activeSupply(uint256 classId, uint256 nonceId) public view override returns (uint256) { return _classes[classId].nonces[nonceId]._activeSupply; } function burnedSupply(uint256 classId, uint256 nonceId) public view override returns (uint256) { return _classes[classId].nonces[nonceId]._burnedSupply; } function redeemedSupply(uint256 classId, uint256 nonceId) public view override returns (uint256) { return _classes[classId].nonces[nonceId]._redeemedSupply; } function balanceOf( address account, uint256 classId, uint256 nonceId ) public view override returns (uint256) { require( account != address(0), "ERC3475: balance query for the zero address" ); return _classes[classId].nonces[nonceId]._balances[account]; } function classMetadata(uint256 metadataId) external view override returns (Metadata memory) { return (_classMetadata[metadataId]); } function nonceMetadata(uint256 classId, uint256 metadataId) external view override returns (Metadata memory) { return (_classes[classId]._nonceMetadata[metadataId]); } function classValues(uint256 classId, uint256 metadataId) external view override returns (Values memory) { return (_classes[classId]._values[metadataId]); } function nonceValues(uint256 classId, uint256 nonceId, uint256 metadataId) external view override returns (Values memory) { return (_classes[classId].nonces[nonceId]._values[metadataId]); } /** determines the progress till the redemption of the bonds is valid (based on the type of bonds class). * @notice ProgressAchieved and `progressRemaining` is abstract. For e.g. we are giving time passed and time remaining. */ function getProgress(uint256 classId, uint256 nonceId) public view override returns (uint256 progressAchieved, uint256 progressRemaining){ uint256 issuanceDate = _classes[classId].nonces[nonceId]._values[0].uintValue; uint256 maturityDate = issuanceDate + _classes[classId].nonces[nonceId]._values[5].uintValue; // check whether the bond is being already initialized: progressAchieved = block.timestamp - issuanceDate; progressRemaining = block.timestamp < maturityDate ? maturityDate - block.timestamp : 0; } /** gets the allowance of the bonds identified by (classId,nonceId) held by _owner to be spend by spender. */ function allowance( address _owner, address spender, uint256 classId, uint256 nonceId ) public view virtual override returns (uint256) { return _classes[classId].nonces[nonceId]._allowances[_owner][spender]; } /** checks the status of approval to transfer the ownership of bonds by _owner to operator. */ function isApprovedFor( address _owner, address operator ) public view virtual override returns (bool) { return operatorApprovals[_owner][operator]; } // INTERNALS function _transferFrom( address _from, address _to, IERC3475.Transaction calldata _transaction ) private { Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; require( nonce._balances[_from] >= _transaction._amount, "ERC3475: not enough bond to transfer" ); //transfer balance nonce._balances[_from] -= _transaction._amount; nonce._balances[_to] += _transaction._amount; } function _transferAllowanceFrom( address _operator, address _from, address _to, IERC3475.Transaction calldata _transaction ) private { Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; require( nonce._balances[_from] >= _transaction._amount, "ERC3475: not allowed amount" ); // reducing the allowance and decreasing accordingly. nonce._allowances[_from][_operator] -= _transaction._amount; //transfer balance nonce._balances[_from] -= _transaction._amount; nonce._balances[_to] += _transaction._amount; } function _issue( address _to, IERC3475.Transaction calldata _transaction ) private { Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; //transfer balance nonce._balances[_to] += _transaction._amount; nonce._activeSupply += _transaction._amount; } function _redeem( address _from, IERC3475.Transaction calldata _transaction ) private { Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; // verify whether _amount of bonds to be redeemed are sufficient available for the given nonce of the bonds require( nonce._balances[_from] >= _transaction._amount, "ERC3475: not enough bond to transfer" ); //transfer balance nonce._balances[_from] -= _transaction._amount; nonce._activeSupply -= _transaction._amount; nonce._redeemedSupply += _transaction._amount; } function _burn( address _from, IERC3475.Transaction calldata _transaction ) private { Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; // verify whether _amount of bonds to be burned are sfficient available for the given nonce of the bonds require( nonce._balances[_from] >= _transaction._amount, "ERC3475: not enough bond to transfer" ); //transfer balance nonce._balances[_from] -= _transaction._amount; nonce._activeSupply -= _transaction._amount; nonce._burnedSupply += _transaction._amount; } }