diff --git a/hathor/nanocontracts/blueprint_env.py b/hathor/nanocontracts/blueprint_env.py index 9fbc61022..c2b670da1 100644 --- a/hathor/nanocontracts/blueprint_env.py +++ b/hathor/nanocontracts/blueprint_env.py @@ -249,14 +249,17 @@ def create_deposit_token( amount: int, mint_authority: bool = True, melt_authority: bool = True, + *, + salt: bytes = b'', ) -> TokenUid: """Create a new deposit-based token.""" return self.__runner.syscall_create_child_deposit_token( - token_name, - token_symbol, - amount, - mint_authority, - melt_authority + salt=salt, + token_name=token_name, + token_symbol=token_symbol, + amount=amount, + mint_authority=mint_authority, + melt_authority=melt_authority, ) # XXX: temporary alias @@ -270,14 +273,17 @@ def create_fee_token( amount: int, mint_authority: bool = True, melt_authority: bool = True, + *, + salt: bytes = b'', ) -> TokenUid: """Create a new fee-based token.""" return self.__runner.syscall_create_child_fee_token( - token_name, - token_symbol, - amount, - mint_authority, - melt_authority + salt=salt, + token_name=token_name, + token_symbol=token_symbol, + amount=amount, + mint_authority=mint_authority, + melt_authority=melt_authority, ) @final diff --git a/hathor/nanocontracts/runner/runner.py b/hathor/nanocontracts/runner/runner.py index 90e73f5cb..9c12d3582 100644 --- a/hathor/nanocontracts/runner/runner.py +++ b/hathor/nanocontracts/runner/runner.py @@ -1022,6 +1022,8 @@ def _create_blueprint_instance(self, blueprint_id: BlueprintId, changes_tracker: @_forbid_syscall_from_view('create_deposit_token') def syscall_create_child_deposit_token( self, + *, + salt: bytes, token_name: str, token_symbol: str, amount: int, @@ -1037,7 +1039,7 @@ def syscall_create_child_deposit_token( last_call_record = self.get_current_call_record() parent_id = last_call_record.contract_id cleaned_token_symbol = clean_token_string(token_symbol) - token_id = derive_child_token_id(parent_id, cleaned_token_symbol) + token_id = derive_child_token_id(parent_id, cleaned_token_symbol, salt=salt) token_amount = amount htr_amount = get_deposit_token_deposit_amount(self._settings, token_amount) @@ -1071,6 +1073,8 @@ def syscall_create_child_deposit_token( @_forbid_syscall_from_view('create_fee_token') def syscall_create_child_fee_token( self, + *, + salt: bytes, token_name: str, token_symbol: str, amount: int, diff --git a/hathor/nanocontracts/utils.py b/hathor/nanocontracts/utils.py index d711504df..4fb6f255f 100644 --- a/hathor/nanocontracts/utils.py +++ b/hathor/nanocontracts/utils.py @@ -80,11 +80,12 @@ def derive_child_contract_id(parent_id: ContractId, salt: bytes, blueprint_id: B return ContractId(VertexId(h.digest())) -def derive_child_token_id(parent_id: ContractId, token_symbol: str) -> TokenUid: +def derive_child_token_id(parent_id: ContractId, token_symbol: str, *, salt: bytes = b'') -> TokenUid: """Derive the token id for a token created by a (parent) contract.""" h = hashlib.sha256() h.update(CHILD_TOKEN_ID_PREFIX) h.update(parent_id) + h.update(salt) h.update(token_symbol.encode('utf-8')) return TokenUid(VertexId(h.digest())) diff --git a/tests/nanocontracts/test_token_creation.py b/tests/nanocontracts/test_token_creation.py index 0bee1d251..076e6c014 100644 --- a/tests/nanocontracts/test_token_creation.py +++ b/tests/nanocontracts/test_token_creation.py @@ -34,13 +34,14 @@ def withdraw(self, ctx: Context) -> None: def create_deposit_token( self, ctx: Context, + salt: bytes, token_name: str, token_symbol: str, amount: int, mint_authority: bool, melt_authority: bool, ) -> None: - self.syscall.create_deposit_token(token_name, token_symbol, amount, mint_authority, melt_authority) + self.syscall.create_deposit_token(token_name, token_symbol, amount, mint_authority, melt_authority, salt=salt) class NCNanoContractTestCase(unittest.TestCase): @@ -164,6 +165,8 @@ def test_token_creation_by_vertex(self) -> None: def test_token_creation_by_contract(self) -> None: token_symbol = 'TKA' + salt0 = b'\0' + salt1 = b'\1' dag_builder = TestDAGBuilder.from_manager(self.manager) vertices = dag_builder.build_from_str(f''' @@ -174,23 +177,28 @@ def test_token_creation_by_contract(self) -> None: tx1.nc_method = initialize() tx2.nc_id = tx1 - tx2.nc_method = create_deposit_token("MyToken", "{token_symbol}", 100, false, false) + tx2.nc_method = create_deposit_token("{salt0.hex()}", "MyToken", "{token_symbol}", 100, false, false) tx2.nc_deposit = 3 HTR tx3.nc_id = tx1 - tx3.nc_method = create_deposit_token("MyToken (2)", "{token_symbol}", 50, true, false) + tx3.nc_method = create_deposit_token("{salt0.hex()}", "MyToken (2)", "{token_symbol}", 50, true, false) tx3.nc_deposit = 1 HTR - tx2 < tx3 + tx4.nc_id = tx1 + tx4.nc_method = create_deposit_token("{salt1.hex()}", "MyToken", "{token_symbol}", 30, true, false) + tx4.nc_deposit = 1 HTR + + tx2 < tx3 < tx4 b31 --> tx1 b31 --> tx2 b32 --> tx3 + b32 --> tx4 ''') vertices.propagate_with(self.manager) - tx1, tx2, tx3 = vertices.get_typed_vertices(['tx1', 'tx2', 'tx3'], Transaction) + tx1, tx2, tx3, tx4 = vertices.get_typed_vertices(['tx1', 'tx2', 'tx3', 'tx4'], Transaction) b31, b32 = vertices.get_typed_vertices(['b31', 'b32'], Block) # Uncomment for debugging: @@ -207,6 +215,9 @@ def test_token_creation_by_contract(self) -> None: assert tx3.get_metadata().voided_by == {tx3.hash, NC_EXECUTION_FAIL_ID} assert tx3.get_metadata().nc_execution is NCExecutionState.FAILURE + assert tx4.get_metadata().voided_by is None + assert tx4.get_metadata().nc_execution is NCExecutionState.SUCCESS + assert b31.get_metadata().voided_by is None assert b32.get_metadata().voided_by is None @@ -217,26 +228,35 @@ def test_token_creation_by_contract(self) -> None: reason='NCTokenAlreadyExists', ) - child_token_id = derive_child_token_id(ContractId(VertexId(tx1.hash)), token_symbol) - child_token_balance_key = BalanceKey(nc_id=tx1.hash, token_uid=child_token_id) + child_token_id0 = derive_child_token_id(ContractId(VertexId(tx1.hash)), token_symbol, salt=salt0) + child_token_id1 = derive_child_token_id(ContractId(VertexId(tx1.hash)), token_symbol, salt=salt1) + + child_token_balance_key0 = BalanceKey(nc_id=tx1.hash, token_uid=child_token_id0) + child_token_balance_key1 = BalanceKey(nc_id=tx1.hash, token_uid=child_token_id1) htr_balance_key = BalanceKey(nc_id=tx1.hash, token_uid=settings.HATHOR_TOKEN_UID) - block_storage = self.manager.get_nc_block_storage(b31) - expected_token_info = TokenDescription( - token_id=child_token_id, + block_storage = self.manager.get_nc_block_storage(b32) + assert block_storage.get_token_description(child_token_id0) == TokenDescription( + token_id=child_token_id0, + token_name='MyToken', + token_symbol=token_symbol, + ) + assert block_storage.get_token_description(child_token_id1) == TokenDescription( + token_id=child_token_id1, token_name='MyToken', token_symbol=token_symbol, ) - assert block_storage.get_token_description(child_token_id) == expected_token_info nc_storage = block_storage.get_contract_storage(tx1.hash) assert nc_storage.get_all_balances() == { - child_token_balance_key: Balance(value=100, can_mint=False, can_melt=False), + child_token_balance_key0: Balance(value=100, can_mint=False, can_melt=False), + child_token_balance_key1: Balance(value=30, can_mint=True, can_melt=False), htr_balance_key: Balance(value=2, can_mint=False, can_melt=False), } tokens_index = self.manager.tx_storage.indexes.tokens assert tokens_index.get_token_info(settings.HATHOR_TOKEN_UID).get_total() == ( - settings.GENESIS_TOKENS + 40 * settings.INITIAL_TOKENS_PER_BLOCK - 1 + settings.GENESIS_TOKENS + 40 * settings.INITIAL_TOKENS_PER_BLOCK - 2 ) - assert tokens_index.get_token_info(child_token_id).get_total() == 100 + assert tokens_index.get_token_info(child_token_id0).get_total() == 100 + assert tokens_index.get_token_info(child_token_id1).get_total() == 30