Skip to content

Commit

Permalink
complete stable swap documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
myeung-vsys committed Feb 8, 2022
1 parent 36b372c commit 12ba34f
Show file tree
Hide file tree
Showing 2 changed files with 523 additions and 0 deletions.
337 changes: 337 additions & 0 deletions src/docs/get-started/smart-contracts/v-atomic-swap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,340 @@ sender1.execute_contract(token2_contract_id, token_without_split_helper.withdraw

sender2.execute_contract(token1_contract_id, token_without_split_helper.withdraw_function_index, withdraw2_data_stack)
```

## Cross-chain Swap Example

The following example contains an example of a Eth-VSYS atomic swap, done using <mark>Truffle</mark> to simulate the Ethereum blockchain in a test environment.


> Before you continue, the following example requires some knowledge with the Ethereum Blockchain and Solidity.


First we install Truffle by:

```
$ npm install -g truffle
```

Then create a directory to contain Ethereum smart contracts and cd into it

```
$ mkdir /chosen/path/truffle-swap
$ cd /chosen/path/truffle-swap
```

Then we initialise an empty Truffle project

```
$ truffle init
```

Next we save the following solidity contract as AtomicSwapEther.sol in /chosen/path/truffle-swap/contracts which should be created upon truffle init.


```
pragma solidity ^0.5.0;
contract AtomicSwapEther {
struct Swap {
uint256 timelock;
uint256 value;
address payable ethTrader;
address payable withdrawTrader;
bytes32 secretLock;
bytes secretKey;
}
enum States {
INVALID,
OPEN,
CLOSED,
EXPIRED
}
mapping (bytes32 => Swap) private swaps;
mapping (bytes32 => States) private swapStates;
event Open(bytes32 _swapID, address _withdrawTrader, bytes32 _secretLock);
event Expire(bytes32 _swapID);
event Close(bytes32 _swapID, bytes _secretKey);
modifier onlyInvalidSwaps(bytes32 _swapID) {
require(swapStates[_swapID] == States.INVALID);
_;
}
modifier onlyOpenSwaps(bytes32 _swapID) {
require(swapStates[_swapID] == States.OPEN);
_;
}
modifier onlyClosedSwaps(bytes32 _swapID) {
require(swapStates[_swapID] == States.CLOSED);
_;
}
modifier onlyExpirableSwaps(bytes32 _swapID) {
require(now >= swaps[_swapID].timelock);
_;
}
modifier onlyWithSecretKey(bytes32 _swapID, bytes memory _secretKey) {
// TODO: Require _secretKey length to conform to the spec
require(swaps[_swapID].secretLock == sha256(_secretKey));
_;
}
function open(bytes32 _swapID, address payable _withdrawTrader, bytes32 _secretLock,
uint256 _timelock) public onlyInvalidSwaps(_swapID) payable {
// Store the details of the swap.
Swap memory swap = Swap({
timelock: _timelock,
value: msg.value,
ethTrader: msg.sender,
withdrawTrader: _withdrawTrader,
secretLock: _secretLock,
secretKey: new bytes(0)
});
swaps[_swapID] = swap;
swapStates[_swapID] = States.OPEN;
// Trigger open event.
emit Open(_swapID, _withdrawTrader, _secretLock);
}
function close(bytes32 _swapID, bytes memory _secretKey) public onlyOpenSwaps(_swapID)
onlyWithSecretKey(_swapID, _secretKey) {
// Close the swap.
Swap memory swap = swaps[_swapID];
swaps[_swapID].secretKey = _secretKey;
swapStates[_swapID] = States.CLOSED;
// Transfer the ETH funds from this contract to the withdrawing trader.
swap.withdrawTrader.transfer(swap.value);
// Trigger close event.
emit Close(_swapID, _secretKey);
}
function expire(bytes32 _swapID) public onlyOpenSwaps(_swapID) onlyExpirableSwaps(_swapID) {
// Expire the swap.
Swap memory swap = swaps[_swapID];
swapStates[_swapID] = States.EXPIRED;
// Transfer the ETH value from this contract back to the ETH trader.
swap.ethTrader.transfer(swap.value);
// Trigger expire event.
emit Expire(_swapID);
}
function check(bytes32 _swapID) public view returns (uint256 timelock, uint256 value,
address withdrawTrader, bytes32 secretLock) {
Swap memory swap = swaps[_swapID];
return (swap.timelock, swap.value, swap.withdrawTrader, swap.secretLock);
}
function checkSecretKey(bytes32 _swapID) public view onlyClosedSwaps(_swapID) returns (bytes memory secretKey) {
Swap memory swap = swaps[_swapID];
return swap.secretKey;
}
}
```


create a javascript file in the /chosen/path/truffle-swap/migrations called '2_deploy_contracts.js' so that we can automatically deploy contracts on truffle start up.


```
const AtomicSwapEther = artifacts.require("AtomicSwapEther");
module.exports = function(deployer) {
deployer.deploy(AtomicSwapEther);
}
```

The contract can now be automatically deployed into the test environment by


```
$ truffle migrate
```

Next we enter the truffle development environment which automatically creates 10 accounts with a large amount of test Eth.


```
$ truffle develop
```

Save the accounts into a variable


```
truffle(develop)> let accounts = await web3.eth.getAccounts();
```

Set up the atomic swap instance so we can interact with the contract


```
truffle(develop)> const SwapInstance = await AtomicSwapEther.deployed();
```


Truffle allows a very easy method to interact with contracts as if they were functions, on the real Ethereum chain, the correct form of the messages accessing the contracts' code must be ensured.

First let's open a channel and send some Eth to the contract. The variables we need to open a channel are: (bytes32 _swapID, address payable _withdrawTrader, bytes32 _secretLock, uint256 _timelock)

The secret lock here can be obtained by using the Sha256 hash on the string "abc" and adding "0x" to the front, which is Ethereum's way of denoting hexadecimal data. It doesn't seem like web3 has the Sha256 hash function, so you can simply use any other source of Sha256 hashing and add "0x" to the front. Or you can simply copy this secretLock and know the secret is "abc".

```
truffle(develop)> let swapID = web3.utils.fromUtf8("123456789");
truffle(develop)> let withdrawTrader = accounts[1];
truffle(develop)> let secretLock = "0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
truffle(develop)> let timelock = 1000000000000000;
```

Then open the Atomic Swap

```
truffle(develop)> SwapInstance.open(swapID, withdrawTrader, secretLock, timelock, {from: accounts[0], value: web3.utils.toWei('2', 'ether')})
```

Now that we've opened this on Ethereum we can open a similar swap contract on VSYS to complete the swap.


### Import package pyvsystems

```
import pyvsystems as pv
from pyvsystems import Account
from pyvsystems.contract import token_id_from_contract_id
from pyvsystems.crypto import bytes2str, sha256
from pyvsystems.contract_helper import *
```

**Setup**

Ensure valid accounts are used, and the token id and contract id of the token to swap should be known.

```
custom_wrapper = pv.create_api_wrapper('<your_node_ip>', api_key='<your_node_api_key>')
chain = pv.Chain(chain_name='mainnet', chain_id='M', address_version=5, api_wrapper=custom_wrapper)
sender1 = Account(chain=chain, seed='<your_account1_seed>', nonce=0)
sender2 = Account(chain=chain, seed='<your_account2_seed>', nonce=0)
```

We'll test swapping VSYS using the system contract for Ethereum. First we register a swap contract that can store VSYS.

```
atomic_swap_helper = AtomicSwapContractHelper()
system_helper = SystemContractHelper()
system_contract_id = chain.system_contract_id()
system_token_id = token_id_from_contract_id(system_contract_id, 0)
register_swap_data_stack = atomic_swap_helper.register_data_stack_generator(system_token_id)
sender1.register_contract(atomic_swap_helper.contract_object, register_swap_data_stack)
swap_contract_id = "<your_atomic_swap_contract_id>"
```

Next we deposit into the contract

```
deposit_data_stack = system_helper.deposit_data_stack_generator(sender1.address, swap_contract_id, 100000000000)
sender1.execute_contract(system_contract_id, system_helper.deposit_function_index, deposit_data_stack)
```

Before we lock the contract, we must know what the puzzle should be. This can be found by executing the 'check' function in the Ethereum atomic swap contract.

```
truffle(develop)> SwapInstance.check(swapID);
Result {
'0': BN {
negative: 0,
words: [ 13008896, 14901161, <1 empty item> ],
length: 2,
red: null
},
'1': BN {
negative: 0,
words: [ 46661632, 5986771, 444, <1 empty item> ],
length: 3,
red: null
},
'2': '0x386e28ad27f73C1732540C68C08c423AA8e43dCc',
'3': '0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
timelock: BN {
negative: 0,
words: [ 13008896, 14901161, <1 empty item> ],
length: 2,
red: null
},
value: BN {
negative: 0,
words: [ 46661632, 5986771, 444, <1 empty item> ],
length: 3,
red: null
},
withdrawTrader: '0x386e28ad27f73C1732540C68C08c423AA8e43dCc',
secretLock: '0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
}
```

As we can see, the 'secretLock' is seen to be "0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad". However, the "0x" in front is a convention on the Ethereum network to represent hexadecimal. Since VSYS prefers to use base58 encoded strings, we must first obtain the pure byte form of the puzzle after dropping the "0x", and then encode it using base58.

```
puzzle_hexstring = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
puzzle_bytes = bytes.fromhex(puzzle_hexstring)
encoded_secret_hash_string = bytes2str(base58.b58encode(puzzle_bytes))
swap_amount_data_entry = DataEntry(1000000, Type.amount)
swap_recipient_data_entry = DataEntry(sender2.address, Type.address)
swap_puzzle_data_entry = DataEntry(encoded_secret_hash_string, Type.short_bytes_string)
swap_timestamp_data_entry = DataEntry(17000000000000000000, Type.timestamp)
swap_lock_data_stack = [swap_amount_data_entry, swap_recipient_data_entry, swap_puzzle_data_entry, swap_timestamp_data_entry]
lock_response = sender1.execute_contract(swap_contract_id, 0, swap_lock_data_stack)
```

Once the contract is locked, the other user can solve the lock, which reveals the secret key.

```
lock_txid = lock_response['id']
swap_lock_txid_data_entry = DataEntry(lock_txid, Type.short_bytes_string)
swap_lock_key_data_entry = DataEntry("abc", Type.short_bytes)
swap_solve_data_stack = [swap_lock_txid_data_entry, swap_lock_key_data_entry]
solve_response = sender2.execute_contract(swap_contract_id, 1, swap_solve_data_stack)
```

We can query the blockchain for the function data in the execute contract, which must contain the correct secret key if the lock gets opened.


```
solve_function_data = chain.api_wrapper.request('transactions/info/%s'%(solve_response['id']))['functionData']
solve_key = bytes2str(base58.b58decode(data_entry_from_base58_str(solve1_function_data)[1].data))
```

The secret is now revealed to be "abc" which can be used to close the Ethereum smart contract. Note that the Ethereum atomic swap implementation uses the hex representation of the string, so we will need to change it from Utf8 representation.


```
truffle(develop)> let secret_key = web3.utils.fromUtf8("abc");
truffle(develop)> SwapInstance.close(swapID, secret_key, {from: accounts[1]});
```
Loading

0 comments on commit 12ba34f

Please sign in to comment.