Skip to content

Commit

Permalink
Merge pull request #552 from BoostryJP/feature/#546
Browse files Browse the repository at this point in the history
Add currency attributes to ibetStraightBond
  • Loading branch information
YoshihitoAso authored Oct 31, 2023
2 parents 4b08b4c + 812ea18 commit baa0f3c
Show file tree
Hide file tree
Showing 21 changed files with 1,187 additions and 237 deletions.
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
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", (), ""
)
self.interest_rate = float(
Decimal(str(ContractUtils.call_function(contract, "interestRate", (), 0)))
* Decimal("0.0001")
)
self.interest_payment_currency = ContractUtils.call_function(
contract, "interestPaymentCurrency", (), ""
)
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", (), ""
)
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
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)
).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
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

@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

0 comments on commit baa0f3c

Please sign in to comment.