Skip to content

Commit

Permalink
Add Sign remote fix #7
Browse files Browse the repository at this point in the history
  • Loading branch information
laalaguer committed Jan 11, 2022
1 parent 1234cb5 commit ff0354a
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 5 deletions.
59 changes: 57 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ print(res)
# >>> {'id': '0x51222328b7395860cb9cc6d69d822cf31056851b5694eeccc9f243021eecd547'}
```

## Send VET and VTHO
## Send VET and VTHO (or any vip180 token)
```python
from thor_requests.connect import Connect
from thor_requests.wallet import Wallet
Expand All @@ -270,13 +270,25 @@ connector.transfer_vtho(
vtho_in_wei=3 * (10 ** 18)
)

# Transfer 3 OCE to 0x0000000000000000000000000000000000000000
connector.transfer_token(
_wallet,
to='0x0000000000000000000000000000000000000000',
token_contract_addr='0x0ce6661b4ba86a0ea7ca2bd86a0de87b0b860f14', # OCE smart contract
amount_in_wei=3 * (10 ** 18)
)

# Check VET or VTHO balance of an address: 0x0000000000000000000000000000000000000000
amount_vet = connector.get_vet_balance('0x0000000000000000000000000000000000000000')
amount_vtho = connector.get_vtho_balance('0x0000000000000000000000000000000000000000')
```

## VIP-191 Fee Delegation Feature (I)
```python
# Sign a local transaction if you have:
# 1) Wallet to originate the transaction
# 2) Wallet to pay for the gas fee

from thor_requests.connect import Connect
from thor_requests.wallet import Wallet
from thor_requests.contract import Contract
Expand Down Expand Up @@ -310,9 +322,52 @@ print(res)
```

## VIP-191 Fee Delegation Feature (II)
```python
# Sign a remotely received raw transaction if you have:
# 1) A judgment function to decide if you want to sign the raw tx or not.
# 2) A sponsor wallet to pay for the gas fee

# On user's side, create a transaction body
delegated_body = {
"chainTag": 1,
"blockRef": "0x00000000aabbccdd",
"expiration": 32,
"clauses": [
{
"to": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed",
"value": 10000,
"data": "0x000000606060"
}
],
"gasPriceCoef": 128,
"gas": 21000,
"dependsOn": None,
"nonce": 12345678,
"reserved": {
"features": 1
}
}

delegated_tx = transaction.Transaction(delegated_body)
raw_tx = '0x' + delegated_tx.encode().hex()

# We give an example judgment function that allows all transaction to be signed
def allPass(tx_payer:str, tx_origin:str, transaction):
''' Run analyze on the given params, shall return bool, error_message '''
return True, ''

your_sponsor_wallet = ... # your local sponsor wallet
tx_origin_address = ... # '0x...' string, the transaction's originator
# Sign it with a local sponsor wallet
result = utils.sign_delegated_tx(your_sponsor_wallet, tx_origin_address, raw_tx, False, allPass)
print(result['signature']) # This is the sponsor signature you are looking for
```


## VIP-191 Fee Delegation Feature (III)

```python
# Send VET or VTHO using fee delegation
# Quickly send VET or VTHO using fee delegation

from thor_requests.connect import Connect
from thor_requests.wallet import Wallet
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setuptools.setup(
name="thor-requests",
version="1.1.2",
version="1.2.0",
author="laalaguer",
author_email="laalaguer@gmail.com",
description="Simple network VeChain SDK for human to interact with the blockchain",
Expand Down
72 changes: 72 additions & 0 deletions tests/test_fee_delegation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
vtho_contract,
)
from thor_requests import utils
from thor_devkit import transaction


def test_vet_transfer(solo_connector, solo_wallet, clean_wallet):
Expand Down Expand Up @@ -97,3 +98,74 @@ def test_transfer_vtho_easy(
clean_wallet.getAddress()
)
assert updated_balance == 0


def test_sign_fee_delegation_allow(
solo_wallet,
clean_wallet
):
delegated_body = {
"chainTag": 1,
"blockRef": "0x00000000aabbccdd",
"expiration": 32,
"clauses": [
{
"to": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed",
"value": 10000,
"data": "0x000000606060"
}
],
"gasPriceCoef": 128,
"gas": 21000,
"dependsOn": None,
"nonce": 12345678,
"reserved": {
"features": 1
}
}

delegated_tx = transaction.Transaction(delegated_body)
raw_tx = '0x' + delegated_tx.encode().hex()

def allPass(tx_payer:str, tx_origin:str, transaction):
return True, ''

result = utils.sign_delegated_tx(solo_wallet, clean_wallet.getAddress(), raw_tx, False, allPass)
assert len(result['signature']) > 0
assert len(result['error_message']) == 0


def test_sign_fee_delegation_reject(
solo_wallet,
clean_wallet
):
''' Reject the tx sign '''
delegated_body = {
"chainTag": 1,
"blockRef": "0x00000000aabbccdd",
"expiration": 32,
"clauses": [
{
"to": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed",
"value": 10000,
"data": "0x000000606060"
}
],
"gasPriceCoef": 128,
"gas": 21000,
"dependsOn": None,
"nonce": 12345678,
"reserved": {
"features": 1
}
}

delegated_tx = transaction.Transaction(delegated_body)
raw_tx = '0x' + delegated_tx.encode().hex()

def allPass(tx_payer:str, tx_origin:str, transaction):
return False, 'I just dont allow this tx to pass'

result = utils.sign_delegated_tx(solo_wallet, clean_wallet.getAddress(), raw_tx, False, allPass)
assert len(result['signature']) == 0
assert result['error_message'] == 'I just dont allow this tx to pass'
25 changes: 25 additions & 0 deletions tests/test_vtho.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,31 @@ def test_transfer_vtho_easy(
assert updated_balance == 3 * (10 ** 18)


def test_transfer_vip180_easy(
solo_connector,
solo_wallet,
clean_wallet,
vtho_contract_address,
vtho_contract
):
# Check the sender's vtho balance
sender_balance = solo_connector.get_vtho_balance(solo_wallet.getAddress())
assert sender_balance > 0

# Check the receiver's vtho balance
receiver_balance = solo_connector.get_vtho_balance(clean_wallet.getAddress())
assert receiver_balance == 0

# Do the vtho transfer (via common transfer token function)
res = solo_connector.transfer_token(solo_wallet, clean_wallet.getAddress(), vtho_contract_address, 3 * (10 ** 18))
tx_id = res["id"]
receipt = solo_connector.wait_for_tx_receipt(tx_id)

# Check the receiver's vtho balance
updated_balance = solo_connector.get_vtho_balance(clean_wallet.getAddress())
assert updated_balance == 3 * (10 ** 18)


def test_transfer_vtho_hard(
solo_connector,
solo_wallet,
Expand Down
23 changes: 23 additions & 0 deletions thor_requests/connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,3 +673,26 @@ def transfer_vtho(self, wallet: Wallet, to: str, vtho_in_wei: int = 0, gas_payer
'''
_contract = Contract({"abi": json.loads(VTHO_ABI)})
return self.transact(wallet, _contract, 'transfer', [to, vtho_in_wei], VTHO_ADDRESS, gas_payer=gas_payer)

def transfer_token(self, wallet: Wallet, to: str, token_contract_addr: str, amount_in_wei: int = 0, gas_payer: Wallet = None) -> dict:
'''
Convenient function: do a pure vip180 token transfer
Parameters
----------
wallet : Wallet
The sender's wallet
to : str
The receiver's address
token_contract_addr : str
The token's smart contract address
amount_in_wei : int, optional
Amount of token (in Wei) to send to receiver, by default 0
Returns
-------
dict
See post_tx()
'''
_contract = Contract({"abi": json.loads(VTHO_ABI)})
return self.transact(wallet, _contract, 'transfer', [to, amount_in_wei], token_contract_addr, gas_payer=gas_payer)
57 changes: 55 additions & 2 deletions thor_requests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


import secrets
from typing import List, Union
from typing import Callable, List, Union

from thor_devkit import abi, cry, transaction
from thor_devkit.cry import address, secp256k1
Expand Down Expand Up @@ -341,4 +341,57 @@ def suggest_gas_for_tx(vm_gas: int, tx_body: dict) -> int:
tx_obj = calc_tx_unsigned(tx_body)
intrincis_gas = tx_obj.get_intrinsic_gas()
supposed_safe_gas = calc_gas(vm_gas, intrincis_gas)
return supposed_safe_gas
return supposed_safe_gas


def sign_delegated_tx(payer: Wallet, tx_origin: str, raw_tx: str, tx_origin_signed:bool, judgment_fn: Callable) -> dict:
'''
Sign a remote extenal delegated fee transaction, according to VIP-191 and VIP-201 standard.
You should pass in a judgement function to decide whether sign the transaction or not.
Parameters
----------
payer : Wallet
The gas payer
tx_origin : str
The transaction origin (address)
raw_tx : str
'0x' prefixed raw transaction. ('0x.....')
tx_origin_signed : bool
Whether or not origin has signed the tx (should match the raw_tx status you send in)
judgment_fn : Callable
Accepts the params of (tx_payer:str, tx_origin:str, transaction:Transaction)
And returns a result of (success:book, error_message:str)
Returns
-------
dict
If successfully signed return {'signature': '0x...', 'error_message': ''}
If failed to sign return {'signature': '', 'error_message': 'some text here'}
Raises
------
AttributeError
Origin address not valid
AttributeError
raw_tx is not delegated tx
Exception
If any error occurs during unpacking raw_tx
'''
# Check tx_origin address
# Raise exception if not valid
if not address.is_address(tx_origin):
raise AttributeError(f'tx_origin: {tx_origin} not valid address')
# Decode raw_tx back to transaction object or body
# Raise exception if not valid
tx = transaction.Transaction.decode(raw=bytes.fromhex(raw_tx.lstrip('0x')), unsigned=(not tx_origin_signed))
if not tx.is_delegated():
raise AttributeError(f'raw_tx: is not delegated tx')
# judge the (tx_payer, tx_origin, transaction), decide if can sign or not
should_sign, error_message = judgment_fn(payer.getAddress(), tx_origin, tx)

if should_sign == True:
payerHash = tx.get_signing_hash(delegate_for=tx_origin.lower())
payerSigBytes = payer.sign(payerHash)
return { 'signature': '0x' + payerSigBytes.hex(), 'error_message': '' }
else:
return { 'signature': '', 'error_message': error_message }

0 comments on commit ff0354a

Please sign in to comment.