Xian Smart Contract Development - Cursor Rules
Add this skill
npx mdskills install PatrickJS/cursor-xian-smart-contracts-cursor-rulesComprehensive Python-based blockchain smart contract development guide with detailed examples
1# Xian Smart Contract Development - Cursor Rules23XIAN is the currency of the Xian blockchain.4Never mention TAU or Lamden.56## Contract Structure78### Basic Structure9- Smart contracts are written in native Python without transpilation10- Contract names must follow the pattern: `^con_[a-z][a-z0-9_]*$`11- Contract names must start with 'con_' prefix (except system contracts like 'currency')12- Contract names must be lowercase, only contain letters, numbers and underscores after prefix13- Contract names must be max 64 characters1415### Naming Conventions16- You cannot use '_' as a prefix for variables or functions (e.g., `_private_var` is not allowed)17- Follow standard Python naming conventions otherwise18- Use descriptive names for clarity19- A contract can not be deployed by another contract2021### Function Types22- `@export` decorator defines public functions callable by any user or contract23- `@construct` decorator defines initialization function executed once at contract submission (optional)24- Functions without decorators are private and can only be called by the contract itself25- Functions with `@export` can call private functions internally2627### Constructor Arguments28- Optional arguments can be provided to the `@construct` function29- Initial state can be setup using these arguments3031## State Management3233### Variable34- `Variable` is a way to define a singular state variable in the contract35- Use `variable.set(value)` to modify36- Use `variable.get()` to retrieve3738```python39my_var = Variable()4041@construct42def seed():43 my_var.set(0) # Initialize variable4445@export46def increment():47 my_var.set(my_var.get() + 1)48```4950### Hash51- `Hash` is a key-value store for the contract52- Default value can be specified with `Hash(default_value=0)`53- Access through dictionary-like syntax: `hash[key] = value` and `hash[key]`54- Supports nested keys with tuple: `hash[key1, key2] = value`5556```python57my_hash = Hash(default_value=0)5859@export60def set_value(key: str, value: int):61 my_hash[key] = value6263@export64def get_value(key: str):65 return my_hash[key]66```6768#### Illegal Delimiters69":" and "." cannot be used in Variable or Hash keys.7071### Foreign State Access72- `ForeignHash` provides read-only access to a Hash from another contract73- `ForeignVariable` provides read-only access to a Variable from another contract7475```python76token_balances = ForeignHash(foreign_contract='con_my_token', foreign_name='balances')77foundation_owner = ForeignVariable(foreign_contract='foundation', foreign_name='owner')78```7980## Context Variables8182### ctx.caller83- The identity of the person or contract calling the function84- Changes when a contract calls another contract's function85- Used for permission checks in token contracts8687### ctx.signer88- The top-level user who signed the transaction89- Remains constant throughout transaction execution90- Only used for security guards/blacklisting, not for account authorization9192### ctx.this93- The identity/name of the current contract94- Never changes95- Useful when the contract needs to refer to itself9697### ctx.owner98- Owner of the contract, optional field set at time of submission99- Only the owner can call exported functions if set100- Can be changed with `ctx.owner = new_owner`101102### ctx.entry103- Returns tuple of (contract_name, function_name) of the original entry point104- Helps identify what contract and function initiated the call chain105106## Built-in Variables107108### Time and Blockchain Information109- `now` - Returns the current datetime110- `block_num` - Returns the current block number, useful for block-dependent logic111- `block_hash` - Returns the current block hash, can be used as a source of randomness112113Example usage:114```python115@construct116def seed():117 submission_time = Variable()118 submission_block_num = Variable()119 submission_block_hash = Variable()120121 # Store blockchain state at contract creation122 submission_time.set(now)123 submission_block_num.set(block_num)124 submission_block_hash.set(block_hash)125```126127## Imports and Contract Interaction128129### Importing Contracts130- Use `importlib.import_module(contract_name)` for dynamic contract imports131- Static contract imports can be done with `import <contract_name>`132- Only use 'import' syntax for contracts, not for libraries or Python modules133- Trying to import standard libraries will not work within a contract (they're automatically available)134- Dynamic imports are preferred when the contract name is determined at runtime135- Can enforce interface with `importlib.enforce_interface()`136- NEVER import anything other than a contract.137- ALL contracting libraries are available globally138- NEVER IMPORT importlib. It is already available globally.139140```python141@export142def interact_with_token(token_contract: str, recipient: str, amount: float):143 token = importlib.import_module(token_contract)144145 # Define expected interface146 interface = [147 importlib.Func('transfer', args=('amount', 'to')),148 importlib.Var('balances', Hash)149 ]150151 # Enforce interface152 assert importlib.enforce_interface(token, interface)153154 # Call function on other contract155 token.transfer(amount=amount, to=recipient)156```157158## Error Handling159160### Assertions161- Use `assert` statements for validation and error checking162- Include error messages: `assert condition, "Error message"`163164### No Try/Except165- Exception handling with try/except is not allowed166- Use conditional logic with if/else statements instead167168```python169# DO NOT USE:170try:171 result = 100 / value172except:173 result = 0174175# CORRECT APPROACH:176assert value != 0, "Cannot divide by zero"177result = 100 / value178179# OR180if value == 0:181 result = 0182else:183 result = 100 / value184```185186### Prohibited Built-ins187- `getattr` is an illegal built-in function and must not be used188- Other Python built-ins may also be restricted for security reasons189190## Modules191192### Random193- Seed RNG with `random.seed()`194- Generate random integers with `random.randint(min, max)`195196### Datetime197- Available by default without importing198- Compare timestamps with standard comparison operators199- Use the built-in `now` variable for current time200201### Crypto202- Provides cryptographic functionality using the PyNaCl library under the hood203- Employs the Ed25519 signature scheme for digital signatures204- Main function is `verify` for signature validation205206```python207# Verify a signature208is_valid = crypto.verify(vk, msg, signature)209# Returns True if the signature is valid for the given message and verification key210```211212Example usage in a contract:213```python214@export215def verify_signature(vk: str, msg: str, signature: str):216 # Use the verify function to check if the signature is valid217 is_valid = crypto.verify(vk, msg, signature)218219 # Return the result of the verification220 return is_valid221```222223### Hashlib224- Xian provides a simplified version of hashlib with a different API than Python's standard library225- Does not require setting up an object and updating it with bytes226- Functions directly accept and return hexadecimal strings227228```python229# Hash a hex string with SHA3 (256 bit)230hash_result = hashlib.sha3("68656c6c6f20776f726c64") # hex for "hello world"231232# If not a valid hex string, it will encode the string to bytes first233text_hash = hashlib.sha3("hello world")234235# SHA256 works the same way (SHA2 256-bit, used in Bitcoin)236sha256_result = hashlib.sha256("68656c6c6f20776f726c64")237```238239## Testing240241### Setting Up Tests242- Use Python's unittest framework243- Client available via `from contracting.client import ContractingClient`244- Flush client before and after each test245246### Setting Test Environment247- Pass environment variables like `now` (datetime) in a dictionary248249```python250from contracting.stdlib.bridge.time import Datetime251252env = {"now": Datetime(year=2021, month=1, day=1, hour=0)}253result = self.some_contract.some_fn(some_arg=some_value, environment=env)254```255256### Specifying Signer257- Specify the signer when calling contract functions in tests258259```python260result = self.some_contract.some_fn(some_arg=some_value, signer="some_signer")261```262263## Events264265### Defining Events266- Use `LogEvent` to define events at the top level of a contract267- Each event has a name and a schema of parameters with their types268- Set `idx: True` for parameters that should be indexed for querying269270```python271TransferEvent = LogEvent(272 event="Transfer",273 params={274 "from": {'type': str, 'idx': True},275 "to": {'type': str, 'idx': True},276 "amount": {'type': (int, float, decimal)}277 }278)279280ApprovalEvent = LogEvent(281 event="Approval",282 params={283 "owner": {'type': str, 'idx': True},284 "spender": {'type': str, 'idx': True},285 "amount": {'type': (int, float, decimal)}286 }287)288```289290### Emitting Events291- Call the event variable as a function and pass a dictionary of parameter values292- All parameters defined in the event schema must be provided293- Event parameters must match the specified types294295```python296@export297def transfer(amount: float, to: str):298 sender = ctx.caller299300 # ... perform transfer logic ...301302 # Emit the transfer event303 TransferEvent({304 "from": sender,305 "to": to,306 "amount": amount307 })308```309310### Testing Events311- Use `return_full_output=True` when calling contract functions in tests to capture events312- Access events in the result dictionary's 'events' key313- Assert on event types and parameters in tests314315```python316# In your test function317result = self.contract.transfer(318 amount=100,319 to="recipient",320 signer="sender",321 return_full_output=True322)323324# Verify events325events = result['events']326assert len(events) == 1327assert events[0]['event'] == 'Transfer'328assert events[0]['from'] == 'sender'329assert events[0]['to'] == 'recipient'330assert events[0]['amount'] == 100331```332333### Common Event Types334- Transfer: When value moves between accounts335- Approval: When spending permissions are granted336- Mint/Burn: When tokens are created or destroyed337- StateChange: When significant contract state changes338- ActionPerformed: When important contract actions execute339340## Smart Contract Testing Best Practices341342### Test Structure343- Use Python's unittest framework for structured testing344- Create a proper test class that inherits from `unittest.TestCase`345- Implement `setUp` and `tearDown` methods to isolate tests346- Define the environment and chain ID in setUp for consistent testing347348```python349class TestMyContract(unittest.TestCase):350 def setUp(self):351 # Bootstrap the environment352 self.chain_id = "test-chain"353 self.environment = {"chain_id": self.chain_id}354 self.deployer_vk = "test-deployer"355356 # Initialize the client357 self.client = ContractingClient(environment=self.environment)358 self.client.flush()359360 # Load and submit the contract361 with open('path/to/my_contract.py') as f:362 code = f.read()363 self.client.submit(code, name="my_contract", constructor_args={"owner": self.deployer_vk})364365 # Get contract instance366 self.contract = self.client.get_contract("my_contract")367368 def tearDown(self):369 # Clean up after each test370 self.client.flush()371```372373### Test Organization374- Group tests by functionality using descriptive method names375- Follow the Given-When-Then pattern for clear test cases376- Test both positive paths and error cases377- Define all variables within the test, not in setUp378- Define all variables and parameters used by a test WITHIN THE TEST, not within setUp379- This ensures test isolation and prevents unexpected side effects between tests380381```python382def test_transfer_success(self):383 # GIVEN a sender with balance384 sender = "alice"385 self.contract.balances[sender] = 1000386387 # WHEN a transfer is executed388 result = self.contract.transfer(amount=100, to="bob", signer=sender)389390 # THEN the balances should be updated correctly391 self.assertEqual(self.contract.balances["bob"], 100)392 self.assertEqual(self.contract.balances[sender], 900)393```394395### Testing for Security Vulnerabilities396397#### 1. Authorization and Access Control398- Test that only authorized users can perform restricted actions399- Verify that contract functions check `ctx.caller` or `ctx.signer` appropriately400401```python402def test_change_metadata_unauthorized(self):403 # GIVEN a non-operator trying to change metadata404 with self.assertRaises(Exception):405 self.contract.change_metadata(key="name", value="NEW", signer="attacker")406```407408#### 2. Replay Attack Protection409- Test that transaction signatures cannot be reused410- Verify nonce mechanisms or one-time-use permits411412```python413def test_permit_double_spending(self):414 # GIVEN a permit already used once415 self.contract.permit(owner="alice", spender="bob", value=100, deadline=deadline,416 signature=signature)417418 # WHEN the permit is used again419 # THEN it should fail420 with self.assertRaises(Exception):421 self.contract.permit(owner="alice", spender="bob", value=100,422 deadline=deadline, signature=signature)423```424425#### 3. Time-Based Vulnerabilities426- Test behavior around time boundaries (begin/end dates)427- Test with different timestamps using the environment parameter428429```python430def test_time_sensitive_function(self):431 # Test with time before deadline432 env = {"now": Datetime(year=2023, month=1, day=1)}433 result = self.contract.some_function(signer="alice", environment=env)434 self.assertTrue(result)435436 # Test with time after deadline437 env = {"now": Datetime(year=2024, month=1, day=1)}438 with self.assertRaises(Exception):439 self.contract.some_function(signer="alice", environment=env)440```441442#### 4. Balance and State Checks443- Verify state changes after operations444- Test for correct balance updates after transfers445- Ensure state consistency through complex operations446447```python448def test_transfer_balances(self):449 # Set initial balances450 self.contract.balances["alice"] = 1000451 self.contract.balances["bob"] = 500452453 # Perform transfer454 self.contract.transfer(amount=300, to="bob", signer="alice")455456 # Verify final balances457 self.assertEqual(self.contract.balances["alice"], 700)458 self.assertEqual(self.contract.balances["bob"], 800)459```460461#### 5. Signature Validation462- Test with valid and invalid signatures463- Test with modified parameters to ensure signatures aren't transferable464465```python466def test_signature_validation(self):467 # GIVEN a properly signed message468 signature = wallet.sign_msg(msg)469470 # WHEN using the correct parameters471 result = self.contract.verify_signature(msg=msg, signature=signature,472 public_key=wallet.public_key)473474 # THEN verification should succeed475 self.assertTrue(result)476477 # BUT when using modified parameters478 with self.assertRaises(Exception):479 self.contract.verify_signature(msg=msg+"tampered", signature=signature,480 public_key=wallet.public_key)481```482483#### 6. Edge Cases and Boundary Conditions484- Test with zero values, max values, empty strings485- Test operations at time boundaries (exactly at deadline)486- Test with invalid inputs and malformed data487488```python489def test_edge_cases(self):490 # Test with zero amount491 with self.assertRaises(Exception):492 self.contract.transfer(amount=0, to="receiver", signer="sender")493494 # Test with negative amount495 with self.assertRaises(Exception):496 self.contract.transfer(amount=-100, to="receiver", signer="sender")497```498499#### 7. Capturing and Verifying Events500- Use `return_full_output=True` to capture events501- Verify event emissions and their parameters502503```python504def test_event_emission(self):505 # GIVEN a setup for transfer506 sender = "alice"507 receiver = "bob"508 amount = 100509 self.contract.balances[sender] = amount510511 # WHEN executing with return_full_output512 result = self.contract.transfer(513 amount=amount,514 to=receiver,515 signer=sender,516 return_full_output=True517 )518519 # THEN verify the event was emitted with correct parameters520 self.assertIn('events', result)521 events = result['events']522 self.assertEqual(len(events), 1)523 event = events[0]524 self.assertEqual(event['event'], 'Transfer')525 self.assertEqual(event['data_indexed']['from'], sender)526 self.assertEqual(event['data_indexed']['to'], receiver)527 self.assertEqual(event['data']['amount'], amount)528```529530### Common Exploits to Test For531532#### Reentrancy533- Test that state is updated before external calls534- Verify operations complete atomically535536```python537def test_no_reentrancy_vulnerability(self):538 # Set up the attack scenario (if possible with Xian)539540 # Verify state is properly updated before any external calls541 # For example, check that balances are decreased before tokens are sent542543 # Verify proper operation ordering in the contract544```545546#### Integer Overflow/Underflow547- Test with extremely large numbers548- Test arithmetic operations at boundaries549550```python551def test_integer_boundaries(self):552 # Set a large balance553 self.contract.balances["user"] = 10**20554555 # Test with large transfers556 result = self.contract.transfer(amount=10**19, to="receiver", signer="user")557558 # Verify results are as expected559 self.assertEqual(self.contract.balances["user"], 9*10**19)560 self.assertEqual(self.contract.balances["receiver"], 10**19)561```562563#### Front-Running Protection564- Test mechanisms that prevent frontrunning (e.g., commit-reveal)565- Test deadline-based protections566567```python568def test_front_running_protection(self):569 # Test with deadlines to ensure transactions expire570 deadline = Datetime(year=2023, month=1, day=1)571 current_time = Datetime(year=2023, month=1, day=2) # After deadline572573 with self.assertRaises(Exception):574 self.contract.time_sensitive_operation(575 param1="value",576 deadline=str(deadline),577 environment={"now": current_time}578 )579```580581#### Authorization Bypass582- Test authorization for all privileged operations583- Try to access functions with different signers584585```python586def test_authorization_checks(self):587 # Test admin functions with non-admin signers588 with self.assertRaises(Exception):589 self.contract.admin_function(param="value", signer="regular_user")590591 # Test with proper authorization592 result = self.contract.admin_function(param="value", signer="admin")593 self.assertTrue(result)594```595596### Best Practices Summary597- Test both positive and negative paths598- Test permissions and authorization thoroughly599- Use environment variables to test time-dependent behavior600- Verify event emissions using `return_full_output=True`601- Test against potential replay attacks and signature validation602- Check edge cases and boundary conditions603- Verify state consistency after operations604- Test for common security vulnerabilities
Full transparency — inspect the skill content before installing.