Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add currency attributes to ibetStraightBond #552

Merged
merged 1 commit into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions app/model/blockchain/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,14 @@ def force_unlock(

class IbetStraightBondContract(IbetSecurityTokenInterface):
face_value: int
face_value_currency: str
interest_rate: float
interest_payment_date: List[str]
interest_payment_currency: str
redemption_date: str
redemption_value: int
redemption_value_currency: str
base_fx_rate: float
Comment on lines +397 to +404
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added four new items to the Bond contract.

  • face_value_currency
  • interest_payment_currency
  • redemption_value_currency
  • base_fx_rate

return_date: str
return_amount: str
purpose: str
Expand Down Expand Up @@ -495,20 +499,39 @@ def get(self):

# Set IbetStraightBondToken attribute
self.face_value = ContractUtils.call_function(contract, "faceValue", (), 0)
self.face_value_currency = ContractUtils.call_function(
contract, "faceValueCurrency", (), ""
)
Comment on lines +502 to +504
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value of face_value_currency is "".

self.interest_rate = float(
Decimal(str(ContractUtils.call_function(contract, "interestRate", (), 0)))
* Decimal("0.0001")
)
self.interest_payment_currency = ContractUtils.call_function(
contract, "interestPaymentCurrency", (), ""
)
Comment on lines +509 to +511
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value of interest_payment_currency is "".

self.redemption_date = ContractUtils.call_function(
contract, "redemptionDate", (), ""
)
self.redemption_value = ContractUtils.call_function(
contract, "redemptionValue", (), 0
)
self.redemption_value_currency = ContractUtils.call_function(
contract, "redemptionValueCurrency", (), ""
)
Comment on lines +518 to +520
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value of redemption_value_currency is "".

self.return_date = ContractUtils.call_function(contract, "returnDate", (), "")
self.return_amount = ContractUtils.call_function(
contract, "returnAmount", (), ""
)
try:
_raw_base_fx_rate = ContractUtils.call_function(
contract, "baseFXRate", (), ""
)
if _raw_base_fx_rate is not None and _raw_base_fx_rate != "":
self.base_fx_rate = float(_raw_base_fx_rate)
else:
self.base_fx_rate = 0.0
except ValueError:
self.base_fx_rate = 0.0
Comment on lines +525 to +534
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value of base_fx_rate is 0.0.
Error handling is performed when invalid data that cannot be converted to float is set.

self.purpose = ContractUtils.call_function(contract, "purpose", (), "")
self.memo = ContractUtils.call_function(contract, "memo", (), "")
self.is_redeemed = ContractUtils.call_function(
Expand Down Expand Up @@ -569,6 +592,26 @@ def update(
except Exception as err:
raise SendTransactionError(err)

if data.face_value_currency is not None:
tx = contract.functions.setFaceValueCurrency(
data.face_value_currency
).build_transaction(
{
"chainId": CHAIN_ID,
"from": tx_from,
"gas": TX_GAS_LIMIT,
"gasPrice": 0,
}
)
try:
ContractUtils.send_transaction(transaction=tx, private_key=private_key)
except ContractRevertError:
raise
except TimeExhausted as timeout_error:
raise SendTransactionError(timeout_error)
except Exception as err:
raise SendTransactionError(err)

if data.interest_rate is not None:
_interest_rate = int(Decimal(str(data.interest_rate)) * Decimal("10000"))
tx = contract.functions.setInterestRate(_interest_rate).build_transaction(
Expand Down Expand Up @@ -612,6 +655,26 @@ def update(
except Exception as err:
raise SendTransactionError(err)

if data.interest_payment_currency is not None:
tx = contract.functions.setInterestPaymentCurrency(
data.interest_payment_currency
).build_transaction(
{
"chainId": CHAIN_ID,
"from": tx_from,
"gas": TX_GAS_LIMIT,
"gasPrice": 0,
}
)
try:
ContractUtils.send_transaction(transaction=tx, private_key=private_key)
except ContractRevertError:
raise
except TimeExhausted as timeout_error:
raise SendTransactionError(timeout_error)
except Exception as err:
raise SendTransactionError(err)

if data.redemption_value is not None:
tx = contract.functions.setRedemptionValue(
data.redemption_value
Expand All @@ -630,6 +693,46 @@ def update(
except Exception as err:
raise SendTransactionError(err)

if data.redemption_value_currency is not None:
tx = contract.functions.setRedemptionValueCurrency(
data.redemption_value_currency
).build_transaction(
{
"chainId": CHAIN_ID,
"from": tx_from,
"gas": TX_GAS_LIMIT,
"gasPrice": 0,
}
)
try:
ContractUtils.send_transaction(transaction=tx, private_key=private_key)
except ContractRevertError:
raise
except TimeExhausted as timeout_error:
raise SendTransactionError(timeout_error)
except Exception as err:
raise SendTransactionError(err)

if data.base_fx_rate is not None:
tx = contract.functions.setBaseFXRate(
str(data.base_fx_rate)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert the input value to str and set it.

).build_transaction(
{
"chainId": CHAIN_ID,
"from": tx_from,
"gas": TX_GAS_LIMIT,
"gasPrice": 0,
}
)
try:
ContractUtils.send_transaction(transaction=tx, private_key=private_key)
except ContractRevertError:
raise
except TimeExhausted as timeout_error:
raise SendTransactionError(timeout_error)
except Exception as err:
raise SendTransactionError(err)

if data.transferable is not None:
tx = contract.functions.setTransferable(
data.transferable
Expand Down
14 changes: 14 additions & 0 deletions app/model/blockchain/tx_params/ibet_straight_bond.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@

class UpdateParams(BaseModel):
face_value: Optional[int] = None
face_value_currency: Optional[str] = None
interest_rate: Optional[float] = None
interest_payment_date: Optional[List[str]] = None
interest_payment_currency: Optional[str] = None
redemption_value: Optional[int] = None
redemption_value_currency: Optional[str] = None
base_fx_rate: Optional[float] = None
transferable: Optional[bool] = None
status: Optional[bool] = None
is_offering: Optional[bool] = None
Expand All @@ -49,6 +53,16 @@ class UpdateParams(BaseModel):
transfer_approval_required: Optional[bool] = None
memo: Optional[str] = None

@field_validator("base_fx_rate")
@classmethod
def base_fx_rate_6_decimal_places(cls, v):
if v is not None:
float_data = float(v * 10**6)
int_data = int(v * 10**6)
if not math.isclose(int_data, float_data):
raise ValueError("base_fx_rate must be rounded to 6 decimal places")
return v

@field_validator("interest_rate")
@classmethod
def interest_rate_4_decimal_places(cls, v):
Expand Down
3 changes: 2 additions & 1 deletion app/model/db/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class Ledger(Base):
{
"created": "string(YYYY/MM/DD)",
"token_name": "string",
"currency": "string",
"headers": [],
"details": [
{
Expand All @@ -75,7 +76,7 @@ class Ledger(Base):


class LedgerDetailsData(Base):
"""Ledger Details Data"""
"""Holder data outside of Blockchain"""

__tablename__ = "ledger_details_data"

Expand Down
1 change: 1 addition & 0 deletions app/model/schema/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class RetrieveLedgerHistoryResponse(BaseModel):

created: str
token_name: str
currency: str
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value is "".
Although the item itself does not exist in the data created with the old version, the default value (i.e. "") is returned as the API return value.

headers: Optional[List[dict]] = None
details: List[RetrieveLedgerDetailsHistoryResponse]
footers: Optional[List[dict]] = None
Expand Down
49 changes: 45 additions & 4 deletions app/model/schema/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,32 @@
from .position import LockEvent
from .types import EMPTY_str, MMDD_constr, ResultSet, SortOrder, YYYYMMDD_constr


############################
# REQUEST
############################


class IbetStraightBondCreate(BaseModel):
"""ibet Straight Bond schema (Create)"""

name: str = Field(max_length=100)
total_supply: int = Field(..., ge=0, le=1_000_000_000_000)
face_value: int = Field(..., ge=0, le=5_000_000_000)
face_value_currency: Optional[str] = Field(default=None, min_length=3, max_length=3)
purpose: str = Field(max_length=2000)
symbol: Optional[str] = Field(default=None, max_length=100)
redemption_date: Optional[YYYYMMDD_constr] = None
redemption_value: Optional[int] = Field(default=None, ge=0, le=5_000_000_000)
redemption_value_currency: Optional[str] = Field(
default=None, min_length=3, max_length=3
)
return_date: Optional[YYYYMMDD_constr] = None
return_amount: Optional[str] = Field(default=None, max_length=2000)
interest_rate: Optional[float] = Field(default=None, ge=0.0000, le=100.0000)
interest_payment_date: Optional[list[MMDD_constr]] = None
interest_payment_currency: Optional[str] = Field(
default=None, min_length=3, max_length=3
)
base_fx_rate: Optional[float] = Field(default=None, ge=0.000000)
transferable: Optional[bool] = None
is_redeemed: Optional[bool] = None
status: Optional[bool] = None
Expand All @@ -60,6 +67,18 @@ class IbetStraightBondCreate(BaseModel):
privacy_policy: Optional[str] = Field(default=None, max_length=5000)
transfer_approval_required: Optional[bool] = None

@field_validator("base_fx_rate")
@classmethod
def base_fx_rate_6_decimal_places(cls, v):
if v is not None:
float_data = float(v * 10**6)
int_data = int(v * 10**6)
if not math.isclose(int_data, float_data):
raise ValueError(
"base_fx_rate must be less than or equal to six decimal places"
)
return v
Comment on lines +70 to +80
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of digits after the decimal point is 6.


@field_validator("interest_rate")
@classmethod
def interest_rate_4_decimal_places(cls, v):
Expand Down Expand Up @@ -102,9 +121,17 @@ class IbetStraightBondUpdate(BaseModel):
"""ibet Straight Bond schema (Update)"""

face_value: Optional[int] = Field(None, ge=0, le=5_000_000_000)
face_value_currency: Optional[str] = Field(default=None, min_length=3, max_length=3)
interest_rate: Optional[float] = Field(None, ge=0.0000, le=100.0000)
interest_payment_date: Optional[list[MMDD_constr]] = None
interest_payment_currency: Optional[str] = Field(
default=None, min_length=3, max_length=3
)
redemption_value: Optional[int] = Field(None, ge=0, le=5_000_000_000)
redemption_value_currency: Optional[str] = Field(
default=None, min_length=3, max_length=3
)
base_fx_rate: Optional[float] = Field(default=None, ge=0.000000)
transferable: Optional[bool] = None
status: Optional[bool] = None
is_offering: Optional[bool] = None
Expand All @@ -116,6 +143,18 @@ class IbetStraightBondUpdate(BaseModel):
transfer_approval_required: Optional[bool] = None
memo: Optional[str] = Field(default=None, max_length=10000)

@field_validator("base_fx_rate")
@classmethod
def base_fx_rate_6_decimal_places(cls, v):
if v is not None:
float_data = float(v * 10**6)
int_data = int(v * 10**6)
if not math.isclose(int_data, float_data):
raise ValueError(
"base_fx_rate must be less than or equal to six decimal places"
)
return v

@field_validator("is_redeemed")
@classmethod
def is_redeemed_is_valid(cls, v):
Expand Down Expand Up @@ -461,8 +500,6 @@ class ListTokenOperationLogHistoryQuery:
############################
# RESPONSE
############################


class TokenAddressResponse(BaseModel):
"""token address"""

Expand All @@ -479,13 +516,17 @@ class IbetStraightBondResponse(BaseModel):
symbol: str
total_supply: int
face_value: int
face_value_currency: str
redemption_date: str
redemption_value: int
redemption_value_currency: str
return_date: str
return_amount: str
purpose: str
interest_rate: float
interest_payment_date: list[str]
interest_payment_currency: str
base_fx_rate: float
transferable: bool
is_redeemed: bool
status: bool
Expand Down
4 changes: 4 additions & 0 deletions app/routers/bond.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,12 @@ def issue_token(

# Check need update
update_items = [
"face_value_currency",
"redemption_value_currency",
"interest_rate",
"interest_payment_date",
"interest_payment_currency",
"base_fx_rate",
"transferable",
"status",
"is_offering",
Expand Down
6 changes: 5 additions & 1 deletion app/routers/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,11 @@ def retrieve_ledger_history(
if _ledger is None:
raise HTTPException(status_code=404, detail="ledger does not exist")

resp = _ledger.ledger
resp: dict = _ledger.ledger

# For backward compatibility, set the default value if `currency` is not set.
if resp.get("currency") is None:
resp["currency"] = ""

if latest_flg == 1: # Get the latest personal info
# Get ibet fin token_detail_type
Expand Down
8 changes: 8 additions & 0 deletions app/utils/ledger_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def create_ledger(token_address: str, db: Session):
):
return

# Get ledger template
_template: LedgerTemplate | None = db.scalars(
select(LedgerTemplate)
.where(LedgerTemplate.token_address == token_address)
Expand All @@ -68,6 +69,12 @@ def create_ledger(token_address: str, db: Session):
if _template is None:
return

# Get currency code only for BOND tokens
currency = "" # default
if _token.type == TokenType.IBET_STRAIGHT_BOND.value:
bond_contract = IbetStraightBondContract(token_address).get()
currency = bond_contract.face_value_currency

# Get ledger details
_details_list: Sequence[LedgerDetailsTemplate] = db.scalars(
select(LedgerDetailsTemplate)
Expand Down Expand Up @@ -98,6 +105,7 @@ def create_ledger(token_address: str, db: Session):
ledger = {
"created": created_ymd,
"token_name": _template.token_name,
"currency": currency,
"headers": _template.headers,
"details": ledger_details,
"footers": _template.footers,
Expand Down
Loading
Loading