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

Introduce "IssueTokensFromGarage" action #295

Merged
merged 12 commits into from
Aug 27, 2024
4 changes: 4 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ on:
required: true
KMS_KEY_ID:
required: true
ADHOC_KMS_KEY_ID:
Copy link
Member

Choose a reason for hiding this comment

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

토큰 발행용 계정인걸까요? �ADHOC보단 별도의 정해진 이름이 있으면 좋을것같네요.(지금 당장 고쳐야할 건은 아닙니다.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

네 garage > issue token 하는 계정입니다.
처음에 만든건 예전 IAP 초기에 수동지급 필요할 때 만들어서 adhoc 으로 한건데, 이름이야 뭐 고치면 됩니다 ㅋ

required: true
GOOGLE_CREDENTIAL:
required: true
APPLE_CREDENTIAL:
Expand Down Expand Up @@ -135,6 +137,7 @@ jobs:
ODIN_GQL_URL: ${{ vars.ODIN_GQL_URL }}
HEIMDALL_GQL_URL: ${{ vars.HEIMDALL_GQL_URL }}
KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }}
ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }}
GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }}
GOOGLE_PACKAGE_NAME: ${{ vars.GOOGLE_PACKAGE_NAME }}
APPLE_BUNDLE_ID: ${{ vars.APPLE_BUNDLE_ID }}
Expand Down Expand Up @@ -172,6 +175,7 @@ jobs:
ODIN_GQL_URL: ${{ vars.ODIN_GQL_URL }}
HEIMDALL_GQL_URL: ${{ vars.HEIMDALL_GQL_URL }}
KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }}
ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }}
GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }}
GOOGLE_PACKAGE_NAME: ${{ vars.GOOGLE_PACKAGE_NAME }}
APPLE_BUNDLE_ID: ${{ vars.APPLE_BUNDLE_ID }}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }}
ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }}
GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }}
APPLE_CREDENTIAL: ${{ secrets.APPLE_CREDENTIAL }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
Expand Down Expand Up @@ -71,6 +72,7 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }}
ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }}
GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }}
APPLE_CREDENTIAL: ${{ secrets.APPLE_CREDENTIAL }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
Expand Down Expand Up @@ -111,6 +113,7 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }}
ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }}
GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }}
APPLE_CREDENTIAL: ${{ secrets.APPLE_CREDENTIAL }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/synth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ on:
required: true
KMS_KEY_ID:
required: true
ADHOC_KMS_KEY_ID:
required: true
GOOGLE_CREDENTIAL:
required: true
APPLE_CREDENTIAL:
Expand Down Expand Up @@ -122,6 +124,7 @@ jobs:
ODIN_GQL_URL: ${{ vars.ODIN_GQL_URL }}
HEIMDALL_GQL_URL: ${{ vars.HEIMDALL_GQL_URL }}
KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }}
ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }}
GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }}
GOOGLE_PACKAGE_NAME: ${{ vars.GOOGLE_PACKAGE_NAME }}
APPLE_BUNDLE_ID: ${{ vars.APPLE_BUNDLE_ID }}
Expand Down
1 change: 1 addition & 0 deletions common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Config:

headless: str = "http://localhost"
kms_key_id: Optional[str] = None
adhoc_kms_key_id: Optional[str] = None
golden_dust_request_sheet_id: Optional[str] = None
golden_dust_work_sheet_id: Optional[str] = None
form_sheet: Optional[str] = None
Expand Down
2 changes: 2 additions & 0 deletions common/_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ def sign_tx(self, unsigned_tx: bytes) -> bytes:
return der_encode(seq)



# Deprecated
def derive_address(address: Union[str, bytes], key: Union[str, bytes], get_byte: bool = False) -> Union[bytes, str]:
"""
Derive given address using key.
Expand Down
11 changes: 11 additions & 0 deletions common/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@
"SOULSTONE_1004", # Lil' Fenrir
)

ITEM_FUNGIBLE_ID_DICT = {
"400000": "3991e04dd808dc0bc24b21f5adb7bf1997312f8700daf1334bf34936e8a0813a",
"500000": "00dfffe23964af9b284d121dae476571b7836b8d9e2e5f510d92a840fecc64fe",
"600201": "f8faf92c9c0d0e8e06694361ea87bfc8b29a8ae8de93044b98470a57636ed0e0",
"600202": "08f566bb43570aad34c1790901f824dd5609db880afebd5382fcec054203d92a",
"600203": "",
"800201": "1a755098a2bc0659a063107df62e2ff9b3cdaba34d96b79519f504b996f53820",
"CRYSTAL": "FAV__CRYSTAL",
"RUNE_GOLDENLEAF": "FAV__RUNE_GOLDENLEAF",
}

GQL_DICT = {
PlanetID.ODIN: os.environ.get("ODIN_GQL_URL"),
PlanetID.ODIN_INTERNAL: os.environ.get("ODIN_GQL_URL"),
Expand Down
53 changes: 53 additions & 0 deletions common/lib9c/actions/issue_tokens_from_garage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from typing import List, Optional

from common.lib9c.actions import ActionBase
from common.lib9c.models.fungible_asset_value import FungibleAssetValue


class FavIssueSpec:
def __init__(self, fav: FungibleAssetValue):
self._fav = fav

@property
def plain_value(self):
return \
[
self._fav.plain_value,
None # No Item spec
]


class ItemIssueSpec:
def __init__(self, fungible_item_id: [str | bytes], amount: int):
self.item_id = fungible_item_id if isinstance(fungible_item_id, bytes) else bytes.fromhex(fungible_item_id)
self.amount = amount

@property
def plain_value(self):
return \
[
None, # No FAV spec
[
self.item_id,
self.amount
]
]


class IssueTokensFromGarage(ActionBase):
"""
Python port of `IssueTokensFromGarage` action from lib9c

- type_id: `issue_tokens_from_garage`
- values: List[spec]
- spec: List[FavIssueSpec | ItemIssueSpec]
"""
TYPE_ID: str = "issue_tokens_from_garage"

def __init__(self, *, values: List[FavIssueSpec | ItemIssueSpec], _id: Optional[str] = None):
super().__init__(self.TYPE_ID, _id)
self._values: List[FavIssueSpec | ItemIssueSpec] = values

@property
def _plain_value(self):
return [x.plain_value for x in self._values] if self._values else None
2 changes: 2 additions & 0 deletions common/lib9c/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class InvalidAmountException(Exception):
pass
46 changes: 46 additions & 0 deletions common/lib9c/models/address.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from __future__ import annotations

import hashlib
import hmac

import eth_utils


class Address:
def __init__(self, addr: str):
Expand All @@ -20,5 +25,46 @@ def long_format(self):
def short_format(self):
return self.raw.hex()

def derive(self, key: str) -> Address:
Copy link
Member

Choose a reason for hiding this comment

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

주소유도하는 메서드가 어디 따로 있었던걸로 기억하는데, 둘이 결과가 같을까요? 다르다면 고쳐야할것같고, 같다면 하나로 통일하는게 좋을것같습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

네 저도 어디 만들어둔 것 같아서 찾아봤는데 안보여서 만들었습니다. 혹 정리하다 보이면 이쪽으로 모으는걸 생각하고 있습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

여기 에 있는데 return type 이 좀 다르네요. derive_address() 를 쓰는 곳은 garage address 유도를 위해 사용하는 곳 하나뿐인데, hash 를 만드는 것은 같고, return 할 때 마지막에 digest 하는 부분이 달라 이건 확인해서 통일해두겠습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

새로 만든 코드로 통일할거라서 기존 코드에는 deprecate 표기 먼저 해놨습니다.
실제로 예전 버전 derive 를 사용하는 곳은 한곳인데, 이건 string > Address 로 타입 변경부터 크게 들어가야 해서 별도 이슈로 만들어두겠습니다.

return Address(
self.__checksum_encode(hmac.new(
key.encode("utf-8"),
self.raw,
digestmod=hashlib.sha1
).digest())
)

def __checksum_encode(self, addr: bytes) -> str: # Takes a 20-byte binary address as input
"""
Convert input address to checksum encoded address without prefix "0x"
See [ERC-55](https://eips.ethereum.org/EIPS/eip-55)

:param addr: 20-bytes binary address
:return: checksum encoded address as string
"""
hex_addr = addr.hex()
checksum_buffer = ""

# Treat the hex address as ascii/utf-8 for keccak256 hashing
hashed_address = eth_utils.keccak(text=hex_addr).hex()

# Iterate over each character in the hex address
for nibble_index, character in enumerate(hex_addr):
if character in "0123456789":
# We can't upper-case the decimal digits
checksum_buffer += character
elif character in "abcdef":
# Check if the corresponding hex digit (nibble) in the hash is 8 or higher
hashed_address_nibble = int(hashed_address[nibble_index], 16)
if hashed_address_nibble > 7:
checksum_buffer += character.upper()
else:
checksum_buffer += character
else:
raise eth_utils.ValidationError(
f"Unrecognized hex character {character!r} at position {nibble_index}"
)
return checksum_buffer

def __eq__(self, other: Address):
return self.raw == other.raw
1 change: 1 addition & 0 deletions common/shared_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
# SecureStrings in Parameter Store
PARAMETER_LIST = (
("KMS_KEY_ID", True),
("ADHOC_KMS_KEY_ID", True),
("GOOGLE_CREDENTIAL", True),
("APPLE_CREDENTIAL", True),
("SEASON_PASS_JWT_SECRET", True),
Expand Down
7 changes: 5 additions & 2 deletions common/utils/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ def fetch_secrets(region: str, secret_arn: str) -> Dict:
return json.loads(resp["SecretString"])


def fetch_kms_key_id(stage: str, region: str) -> Optional[str]:
def fetch_kms_key_id(stage: str, region: str, adhoc: bool = False) -> Optional[str]:
Copy link
Member

Choose a reason for hiding this comment

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

코드를 봐서는 서명용 키를 워커마다 다르게 가져가는 식인것같은데, 중복이 좀 되더라도 차라리 키별로 얻어오는 메서드를 아예 명시적으로 나눠버리는게 어떨까 싶네요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이걸 True 로 쓰는 데가 사실상 manual sign 하는 곳 (운영 계정에서 IssueToken + Transfer Assets) 밖에 없기는 한데
근데 저걸 또 가끔은 IAP 판매 계정으로 써야 할 때가 있어서요.
완전히 worker 별로 얘는 이거 쟤는 저거로 고정되어 있지는 않습니다.

Copy link
Member

Choose a reason for hiding this comment

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

A계정으로 B계정의 일을 처리하는게(혹은 반대) 겁나는 상황인건데, 이걸 막을수있을까요? (tx에 들어간 액션따라 허용 주소를 둔다던지)

Copy link
Collaborator Author

@U-lis U-lis Aug 27, 2024

Choose a reason for hiding this comment

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

IssueTokensFromGarage action 의 경우 무조건 context.Signer 주소를 가지고 작업을 하도록 되어 있어서 다른 계정의 재고를 건드리는 일은 없습니다.
그리고 우려하시는 A 계정의 garage > token 작업을 해야 할 때 B 계정으로 서명을 하는 경우에 대해서는

  1. 어차피 이제 모든 garage 는 token 으로 갈 거니 넘어가도 큰 문제는 안 된다
  2. 주소를 잘못 넣으면 garage 가 비어 있어서 에러가 날거다

정도가 발생할 것으로 보여서 크게 걱정은 안했습니다.

client = boto3.client("ssm", region_name=region)
try:
return client.get_parameter(Name=f"{stage}_9c_IAP_KMS_KEY_ID", WithDecryption=True)["Parameter"]["Value"]
return client.get_parameter(
Name=f"{stage}_9c_IAP{'_ADHOC' if adhoc else ''}_KMS_KEY_ID",
WithDecryption=True
)["Parameter"]["Value"]
except Exception as e:
logger.error(e)
return None
120 changes: 120 additions & 0 deletions tests/lib9c/actions/test_issue_tokens_from_garage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from decimal import Decimal

import pytest

from common.lib9c.actions.issue_tokens_from_garage import IssueTokensFromGarage, FavIssueSpec, ItemIssueSpec
from common.lib9c.models.currency import Currency
from common.lib9c.models.fungible_asset_value import FungibleAssetValue

TEST_ID = "0d0d9e0cbc1b11eeb0dc6fd71476142a"
TEST_FUNGIBLE_ITEM_ID = "f8faf92c9c0d0e8e06694361ea87bfc8b29a8ae8de93044b98470a57636ed0e0" # Golden Dust
TEST_FUNGIBLE_ITEM_BINARY = b'\xf8\xfa\xf9,\x9c\r\x0e\x8e\x06iCa\xea\x87\xbf\xc8\xb2\x9a\x8a\xe8\xde\x93\x04K\x98G\nWcn\xd0\xe0'
ACTION_TEST_DATA = [
(
{
"_id": TEST_ID,
"values": [
FavIssueSpec(FungibleAssetValue.from_raw_data("Crystal", 18, amount=Decimal("1000")))
]
},
[
[
[
{
"ticker": "Crystal",
"decimalPlaces": b'\x12',
"minters": None,
},
1000 * 10 ** 18
],
None
],
]
),
(
{
"_id": TEST_ID,
"values": [
ItemIssueSpec(TEST_FUNGIBLE_ITEM_ID, 42)
]
},
[
[
None,
[
TEST_FUNGIBLE_ITEM_BINARY,
42
]
]
]
),
(
{
"_id": TEST_ID,
"values": [
ItemIssueSpec("f8faf92c9c0d0e8e06694361ea87bfc8b29a8ae8de93044b98470a57636ed0e0", 42)
]
},
[
[
None,
[
b'\xf8\xfa\xf9,\x9c\r\x0e\x8e\x06iCa\xea\x87\xbf\xc8\xb2\x9a\x8a\xe8\xde\x93\x04K\x98G\nWcn\xd0\xe0',
42
]
]
]
),
(
{
"_id": TEST_ID,
"values": [
FavIssueSpec(FungibleAssetValue.from_raw_data("Crystal", 18, amount=Decimal("1000"))),
ItemIssueSpec("f8faf92c9c0d0e8e06694361ea87bfc8b29a8ae8de93044b98470a57636ed0e0", 42)
]
},
[
[
[
{
"ticker": "Crystal",
"decimalPlaces": b'\x12',
"minters": None,
},
1000 * 10 ** 18
],
None
],
[
None,
[
b'\xf8\xfa\xf9,\x9c\r\x0e\x8e\x06iCa\xea\x87\xbf\xc8\xb2\x9a\x8a\xe8\xde\x93\x04K\x98G\nWcn\xd0\xe0',
42
]
]
]
)
]


def test_fav_issue_spec():
fav = FungibleAssetValue(Currency("Crystal", 18), Decimal(42))
fav_issue_spec = FavIssueSpec(fav)
assert fav_issue_spec.plain_value[0] == fav.plain_value


@pytest.mark.parametrize("data", [TEST_FUNGIBLE_ITEM_ID, TEST_FUNGIBLE_ITEM_BINARY])
def test_item_issue_spec(data):
item_issue_spec = ItemIssueSpec(data, 42)
assert item_issue_spec.plain_value == [None, [TEST_FUNGIBLE_ITEM_BINARY, 42]]


@pytest.mark.parametrize("test_data", ACTION_TEST_DATA)
def test_action(test_data):
data, expected = test_data
action = IssueTokensFromGarage(**data)
plain_value = action.plain_value
values = plain_value["values"]

assert plain_value["type_id"] == "issue_tokens_from_garage"
assert values == expected
9 changes: 9 additions & 0 deletions tests/lib9c/models/test_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ def test_address(addr):
def test_address_error(addr):
with pytest.raises(ValueError) as e:
Address(addr)


@pytest.mark.parametrize(("key", "result"), [
("key1", "0x518c0044a81c7d3747Ad416Df7227e53233F2e10"),
("key2", "0xEb5cC07Eb104B082C1ae82E018340e989700ca31")
])
def test_address_derive(key, result):
addr = Address("0xa5f7e0bd63AD2749D66380f36Eb33Fe0ba50A27D")
assert addr.derive(key) == Address(result)
Loading
Loading