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
XIAN is the currency of the Xian blockchain. Never mention TAU or Lamden.
^con_[a-z][a-z0-9_]*$_private_var is not allowed)@export decorator defines public functions callable by any user or contract@construct decorator defines initialization function executed once at contract submission (optional)@export can call private functions internally@construct functionVariable is a way to define a singular state variable in the contractvariable.set(value) to modifyvariable.get() to retrievemy_var = Variable()
@construct
def seed():
my_var.set(0) # Initialize variable
@export
def increment():
my_var.set(my_var.get() + 1)
Hash is a key-value store for the contractHash(default_value=0)hash[key] = value and hash[key]hash[key1, key2] = valuemy_hash = Hash(default_value=0)
@export
def set_value(key: str, value: int):
my_hash[key] = value
@export
def get_value(key: str):
return my_hash[key]
":" and "." cannot be used in Variable or Hash keys.
ForeignHash provides read-only access to a Hash from another contractForeignVariable provides read-only access to a Variable from another contracttoken_balances = ForeignHash(foreign_contract='con_my_token', foreign_name='balances')
foundation_owner = ForeignVariable(foreign_contract='foundation', foreign_name='owner')
ctx.owner = new_ownernow - Returns the current datetimeblock_num - Returns the current block number, useful for block-dependent logicblock_hash - Returns the current block hash, can be used as a source of randomnessExample usage:
@construct
def seed():
submission_time = Variable()
submission_block_num = Variable()
submission_block_hash = Variable()
# Store blockchain state at contract creation
submission_time.set(now)
submission_block_num.set(block_num)
submission_block_hash.set(block_hash)
importlib.import_module(contract_name) for dynamic contract importsimport importlib.enforce_interface()@export
def interact_with_token(token_contract: str, recipient: str, amount: float):
token = importlib.import_module(token_contract)
# Define expected interface
interface = [
importlib.Func('transfer', args=('amount', 'to')),
importlib.Var('balances', Hash)
]
# Enforce interface
assert importlib.enforce_interface(token, interface)
# Call function on other contract
token.transfer(amount=amount, to=recipient)
assert statements for validation and error checkingassert condition, "Error message"# DO NOT USE:
try:
result = 100 / value
except:
result = 0
# CORRECT APPROACH:
assert value != 0, "Cannot divide by zero"
result = 100 / value
# OR
if value == 0:
result = 0
else:
result = 100 / value
getattr is an illegal built-in function and must not be usedrandom.seed()random.randint(min, max)now variable for current timeverify for signature validation# Verify a signature
is_valid = crypto.verify(vk, msg, signature)
# Returns True if the signature is valid for the given message and verification key
Example usage in a contract:
@export
def verify_signature(vk: str, msg: str, signature: str):
# Use the verify function to check if the signature is valid
is_valid = crypto.verify(vk, msg, signature)
# Return the result of the verification
return is_valid
# Hash a hex string with SHA3 (256 bit)
hash_result = hashlib.sha3("68656c6c6f20776f726c64") # hex for "hello world"
# If not a valid hex string, it will encode the string to bytes first
text_hash = hashlib.sha3("hello world")
# SHA256 works the same way (SHA2 256-bit, used in Bitcoin)
sha256_result = hashlib.sha256("68656c6c6f20776f726c64")
from contracting.client import ContractingClientnow (datetime) in a dictionaryfrom contracting.stdlib.bridge.time import Datetime
env = {"now": Datetime(year=2021, month=1, day=1, hour=0)}
result = self.some_contract.some_fn(some_arg=some_value, environment=env)
result = self.some_contract.some_fn(some_arg=some_value, signer="some_signer")
LogEvent to define events at the top level of a contractidx: True for parameters that should be indexed for queryingTransferEvent = LogEvent(
event="Transfer",
params={
"from": {'type': str, 'idx': True},
"to": {'type': str, 'idx': True},
"amount": {'type': (int, float, decimal)}
}
)
ApprovalEvent = LogEvent(
event="Approval",
params={
"owner": {'type': str, 'idx': True},
"spender": {'type': str, 'idx': True},
"amount": {'type': (int, float, decimal)}
}
)
@export
def transfer(amount: float, to: str):
sender = ctx.caller
# ... perform transfer logic ...
# Emit the transfer event
TransferEvent({
"from": sender,
"to": to,
"amount": amount
})
return_full_output=True when calling contract functions in tests to capture events# In your test function
result = self.contract.transfer(
amount=100,
to="recipient",
signer="sender",
return_full_output=True
)
# Verify events
events = result['events']
assert len(events) == 1
assert events[0]['event'] == 'Transfer'
assert events[0]['from'] == 'sender'
assert events[0]['to'] == 'recipient'
assert events[0]['amount'] == 100
unittest.TestCasesetUp and tearDown methods to isolate testsclass TestMyContract(unittest.TestCase):
def setUp(self):
# Bootstrap the environment
self.chain_id = "test-chain"
self.environment = {"chain_id": self.chain_id}
self.deployer_vk = "test-deployer"
# Initialize the client
self.client = ContractingClient(environment=self.environment)
self.client.flush()
# Load and submit the contract
with open('path/to/my_contract.py') as f:
code = f.read()
self.client.submit(code, name="my_contract", constructor_args={"owner": self.deployer_vk})
# Get contract instance
self.contract = self.client.get_contract("my_contract")
def tearDown(self):
# Clean up after each test
self.client.flush()
def test_transfer_success(self):
# GIVEN a sender with balance
sender = "alice"
self.contract.balances[sender] = 1000
# WHEN a transfer is executed
result = self.contract.transfer(amount=100, to="bob", signer=sender)
# THEN the balances should be updated correctly
self.assertEqual(self.contract.balances["bob"], 100)
self.assertEqual(self.contract.balances[sender], 900)
ctx.caller or ctx.signer appropriatelydef test_change_metadata_unauthorized(self):
# GIVEN a non-operator trying to change metadata
with self.assertRaises(Exception):
self.contract.change_metadata(key="name", value="NEW", signer="attacker")
def test_permit_double_spending(self):
# GIVEN a permit already used once
self.contract.permit(owner="alice", spender="bob", value=100, deadline=deadline,
signature=signature)
# WHEN the permit is used again
# THEN it should fail
with self.assertRaises(Exception):
self.contract.permit(owner="alice", spender="bob", value=100,
deadline=deadline, signature=signature)
def test_time_sensitive_function(self):
# Test with time before deadline
env = {"now": Datetime(year=2023, month=1, day=1)}
result = self.contract.some_function(signer="alice", environment=env)
self.assertTrue(result)
# Test with time after deadline
env = {"now": Datetime(year=2024, month=1, day=1)}
with self.assertRaises(Exception):
self.contract.some_function(signer="alice", environment=env)
def test_transfer_balances(self):
# Set initial balances
self.contract.balances["alice"] = 1000
self.contract.balances["bob"] = 500
# Perform transfer
self.contract.transfer(amount=300, to="bob", signer="alice")
# Verify final balances
self.assertEqual(self.contract.balances["alice"], 700)
self.assertEqual(self.contract.balances["bob"], 800)
def test_signature_validation(self):
# GIVEN a properly signed message
signature = wallet.sign_msg(msg)
# WHEN using the correct parameters
result = self.contract.verify_signature(msg=msg, signature=signature,
public_key=wallet.public_key)
# THEN verification should succeed
self.assertTrue(result)
# BUT when using modified parameters
with self.assertRaises(Exception):
self.contract.verify_signature(msg=msg+"tampered", signature=signature,
public_key=wallet.public_key)
def test_edge_cases(self):
# Test with zero amount
with self.assertRaises(Exception):
self.contract.transfer(amount=0, to="receiver", signer="sender")
# Test with negative amount
with self.assertRaises(Exception):
self.contract.transfer(amount=-100, to="receiver", signer="sender")
return_full_output=True to capture eventsdef test_event_emission(self):
# GIVEN a setup for transfer
sender = "alice"
receiver = "bob"
amount = 100
self.contract.balances[sender] = amount
# WHEN executing with return_full_output
result = self.contract.transfer(
amount=amount,
to=receiver,
signer=sender,
return_full_output=True
)
# THEN verify the event was emitted with correct parameters
self.assertIn('events', result)
events = result['events']
self.assertEqual(len(events), 1)
event = events[0]
self.assertEqual(event['event'], 'Transfer')
self.assertEqual(event['data_indexed']['from'], sender)
self.assertEqual(event['data_indexed']['to'], receiver)
self.assertEqual(event['data']['amount'], amount)
def test_no_reentrancy_vulnerability(self):
# Set up the attack scenario (if possible with Xian)
# Verify state is properly updated before any external calls
# For example, check that balances are decreased before tokens are sent
# Verify proper operation ordering in the contract
def test_integer_boundaries(self):
# Set a large balance
self.contract.balances["user"] = 10**20
# Test with large transfers
result = self.contract.transfer(amount=10**19, to="receiver", signer="user")
# Verify results are as expected
self.assertEqual(self.contract.balances["user"], 9*10**19)
self.assertEqual(self.contract.balances["receiver"], 10**19)
def test_front_running_protection(self):
# Test with deadlines to ensure transactions expire
deadline = Datetime(year=2023, month=1, day=1)
current_time = Datetime(year=2023, month=1, day=2) # After deadline
with self.assertRaises(Exception):
self.contract.time_sensitive_operation(
param1="value",
deadline=str(deadline),
environment={"now": current_time}
)
def test_authorization_checks(self):
# Test admin functions with non-admin signers
with self.assertRaises(Exception):
self.contract.admin_function(param="value", signer="regular_user")
# Test with proper authorization
result = self.contract.admin_function(param="value", signer="admin")
self.assertTrue(result)
return_full_output=TrueInstall via CLI
npx mdskills install PatrickJS/cursor-xian-smart-contracts-cursor-rulesXian Smart Contracts Cursor Rules is a free, open-source AI agent skill. Xian Smart Contract Development - Cursor Rules
Install Xian Smart Contracts Cursor Rules with a single command:
npx mdskills install PatrickJS/cursor-xian-smart-contracts-cursor-rulesThis downloads the skill files into your project and your AI agent picks them up automatically.
Xian Smart Contracts Cursor Rules works with Cursor. Skills use the open SKILL.md format which is compatible with any AI coding agent that reads markdown instructions.