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

Support v24.9 token features #657

Merged
merged 4 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
47 changes: 34 additions & 13 deletions app/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,46 +31,67 @@ class AppError(Exception):
################################################
# 400_BAD_REQUEST
################################################
class InvalidParameterError(AppError):
class BadRequestError(AppError):
status_code = status.HTTP_400_BAD_REQUEST


class InvalidParameterError(BadRequestError):
code = 1


class SendTransactionError(AppError):
status_code = status.HTTP_400_BAD_REQUEST
class SendTransactionError(BadRequestError):
code = 2


class AuthTokenAlreadyExistsError(AppError):
status_code = status.HTTP_400_BAD_REQUEST
class AuthTokenAlreadyExistsError(BadRequestError):
code = 3


class ResponseLimitExceededError(AppError):
status_code = status.HTTP_400_BAD_REQUEST
class ResponseLimitExceededError(BadRequestError):
code = 4


class Integer64bitLimitExceededError(AppError):
status_code = status.HTTP_400_BAD_REQUEST
class Integer64bitLimitExceededError(BadRequestError):
code = 5


class OperationNotSupportedVersionError(AppError):
class OperationNotSupportedVersionError(BadRequestError):
"""
The token version for which the operation is not supported
"""

status_code = status.HTTP_400_BAD_REQUEST
code = 6


class OperationNotAllowedStateError(AppError):
class TokenNotExistError(BadRequestError):
"""
Operations on non-transferable tokens
"""

code = 7


class NonTransferableTokenError(BadRequestError):
"""
Operations on non-transferable tokens
"""

code = 8


class MultipleTokenTransferNotAllowedError(BadRequestError):
"""
Transfer operations that do not allow specifying multiple tokens at once
"""

code = 9


class OperationNotAllowedStateError(BadRequestError):
"""
Error returned when server-side data is not ready to process the request
"""

status_code = status.HTTP_400_BAD_REQUEST
code_list = [
101, # Transfer approval operations cannot be performed for accounts that do not have personal information registered.
]
Expand Down
93 changes: 15 additions & 78 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,84 +164,21 @@ async def query_validation_exception_handler(request: Request, exc: ValidationEr
)


# 400:InvalidParameterError
@app.exception_handler(InvalidParameterError)
async def invalid_parameter_error_handler(request: Request, exc: InvalidParameterError):
meta = {"code": 1, "title": "InvalidParameterError"}
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({"meta": meta, "detail": exc.args[0]}),
)


# 400:SendTransactionError
@app.exception_handler(SendTransactionError)
async def send_transaction_error_handler(request: Request, exc: SendTransactionError):
meta = {"code": 2, "title": "SendTransactionError"}
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({"meta": meta, "detail": exc.args[0]}),
)


# 400:AuthTokenAlreadyExistsError
@app.exception_handler(AuthTokenAlreadyExistsError)
async def auth_token_already_exists_error_handler(
request: Request, exc: AuthTokenAlreadyExistsError
):
meta = {"code": 3, "title": "AuthTokenAlreadyExistsError"}
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({"meta": meta}),
)


# 400:ResponseLimitExceededError
@app.exception_handler(ResponseLimitExceededError)
async def response_limit_exceeded_error_handler(
request: Request, exc: ResponseLimitExceededError
):
meta = {"code": 4, "title": "ResponseLimitExceededError"}
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({"meta": meta, "detail": exc.args[0]}),
)


# 400:Integer64bitLimitExceededError
@app.exception_handler(Integer64bitLimitExceededError)
async def response_limit_exceeded_error_handler(
request: Request, exc: Integer64bitLimitExceededError
):
meta = {"code": 5, "title": "Integer64bitLimitExceededError"}
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({"meta": meta, "detail": exc.args[0]}),
)


# 400:OperationNotSupportedVersionError
@app.exception_handler(OperationNotSupportedVersionError)
async def operation_not_supported_version_error_handler(
request: Request, exc: OperationNotSupportedVersionError
):
meta = {"code": 6, "title": "OperationNotSupportedVersionError"}
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({"meta": meta, "detail": exc.args[0]}),
)


# 400:OperationNotAllowedStateError
@app.exception_handler(OperationNotAllowedStateError)
async def operation_not_permitted_error_handler(
request: Request, exc: OperationNotAllowedStateError
):
meta = {"code": exc.code, "title": "OperationNotAllowedStateError"}
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({"meta": meta, "detail": exc.args[0]}),
)
# 400:BadRequestError
@app.exception_handler(BadRequestError)
async def bad_request_error_handler(request: Request, exc: BadRequestError):
meta = {"code": exc.code, "title": exc.__class__.__name__}

if len(exc.args) > 0:
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({"meta": meta, "detail": exc.args[0]}),
)
else:
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({"meta": meta}),
)


# 400:ContractRevertError
Expand Down
161 changes: 157 additions & 4 deletions app/model/blockchain/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@
ApproveTransferParams as IbetSecurityTokenApproveTransfer,
BulkTransferParams as IbetSecurityTokenBulkTransferParams,
CancelTransferParams as IbetSecurityTokenCancelTransfer,
ForcedTransferParams as IbetSecurityTokenForcedTransferParams,
ForceUnlockParams as IbetSecurityTokenForceUnlockParams,
LockParams as IbetSecurityTokenLockParams,
RedeemParams as IbetSecurityTokenRedeemParams,
TransferParams as IbetSecurityTokenTransferParams,
)
from app.model.blockchain.tx_params.ibet_share import (
UpdateParams as IbetShareUpdateParams,
Expand Down Expand Up @@ -179,8 +179,11 @@ def __init__(
):
super().__init__(contract_address, contract_name)

async def transfer(
self, data: IbetSecurityTokenTransferParams, tx_from: str, private_key: bytes
async def forced_transfer(
self,
data: IbetSecurityTokenForcedTransferParams,
tx_from: str,
private_key: bytes,
):
"""Transfer ownership"""
try:
Expand Down Expand Up @@ -212,6 +215,48 @@ async def transfer(

return tx_hash

async def bulk_forced_transfer(
self,
data: list[IbetSecurityTokenForcedTransferParams],
tx_from: str,
private_key: bytes,
):
"""Bulk transfer ownership"""
from_list = []
to_list = []
amounts = []

for _d in data:
from_list.append(_d.from_address)
to_list.append(_d.to_address)
amounts.append(_d.amount)

try:
contract = AsyncContractUtils.get_contract(
contract_name=self.contract_name, contract_address=self.token_address
)
tx = await contract.functions.bulkTransferFrom(
from_list, to_list, amounts
).build_transaction(
{
"chainId": CHAIN_ID,
"from": tx_from,
"gas": TX_GAS_LIMIT,
"gasPrice": 0,
}
)
tx_hash, _ = await AsyncContractUtils.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)

return tx_hash

async def bulk_transfer(
self,
data: IbetSecurityTokenBulkTransferParams,
Expand Down Expand Up @@ -291,6 +336,59 @@ async def additional_issue(

return tx_hash

async def bulk_additional_issue(
self,
data: list[IbetSecurityTokenAdditionalIssueParams],
tx_from: str,
private_key: bytes,
):
"""Bulk additional issue"""
target_address_list = []
lock_address_list = []
amounts = []

for _d in data:
target_address_list.append(_d.account_address)
lock_address_list.append(ZERO_ADDRESS)
amounts.append(_d.amount)

try:
contract = AsyncContractUtils.get_contract(
contract_name=self.contract_name, contract_address=self.token_address
)
tx = await contract.functions.bulkIssueFrom(
target_address_list, lock_address_list, amounts
).build_transaction(
{
"chainId": CHAIN_ID,
"from": tx_from,
"gas": TX_GAS_LIMIT,
"gasPrice": 0,
}
)
tx_hash, _ = await AsyncContractUtils.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)

# Delete Cache
db_session = AsyncSession(autocommit=False, autoflush=True, bind=async_engine)
try:
await self.record_attr_update(db_session)
await self.delete_cache(db_session)
await db_session.commit()
except Exception as err:
raise SendTransactionError(err)
finally:
await db_session.close()

return tx_hash

async def redeem(
self, data: IbetSecurityTokenRedeemParams, tx_from: str, private_key: bytes
):
Expand Down Expand Up @@ -334,6 +432,59 @@ async def redeem(

return tx_hash

async def bulk_redeem(
self,
data: list[IbetSecurityTokenRedeemParams],
tx_from: str,
private_key: bytes,
):
"""Redeem a token"""
target_address_list = []
lock_address_list = []
amounts = []

for _d in data:
target_address_list.append(_d.account_address)
lock_address_list.append(ZERO_ADDRESS)
amounts.append(_d.amount)

try:
contract = AsyncContractUtils.get_contract(
contract_name=self.contract_name, contract_address=self.token_address
)
tx = await contract.functions.bulkRedeemFrom(
target_address_list, lock_address_list, amounts
).build_transaction(
{
"chainId": CHAIN_ID,
"from": tx_from,
"gas": TX_GAS_LIMIT,
"gasPrice": 0,
}
)
tx_hash, _ = await AsyncContractUtils.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)

# Delete Cache
db_session = AsyncSession(autocommit=False, autoflush=True, bind=async_engine)
try:
await self.record_attr_update(db_session)
await self.delete_cache(db_session)
await db_session.commit()
except Exception as err:
raise SendTransactionError(err)
finally:
await db_session.close()

return tx_hash

async def approve_transfer(
self, data: IbetSecurityTokenApproveTransfer, tx_from: str, private_key: bytes
):
Expand Down Expand Up @@ -497,7 +648,9 @@ async def create(self, args: list, tx_from: str, private_key: bytes):
else:
raise SendTransactionError("contract is already deployed")

async def get(self):
T = TypeVar("T")

async def get(self) -> T:
"""Get token attributes"""
db_session = AsyncSession(autocommit=False, autoflush=True, bind=async_engine)
try:
Expand Down
2 changes: 1 addition & 1 deletion app/model/blockchain/tx_params/ibet_security_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from app.model import EthereumAddress


class TransferParams(BaseModel):
class ForcedTransferParams(BaseModel):
from_address: EthereumAddress
to_address: EthereumAddress
amount: PositiveInt
Expand Down
Loading
Loading