Skip to content

Latest commit

 

History

History
418 lines (295 loc) · 13.4 KB

RFC-009-SWAP-Basic-ERC20.adoc

File metadata and controls

418 lines (295 loc) · 13.4 KB

ERC20 Basic HTLC Atomic Swap

Note
RFC-Number: 009
Status: Draft
+ Created on: 2019-03-12

1. Description

This RFC defines how to execute a RFC003 SWAP where one of the ledgers is Ethereum and the associated asset is an ERC20 token.

The definition of the Ethereum ledger was introduced in RFC006. The definition of ERC20 token asset was introduced in RFC008.

To fulfil the requirements of RFC003 this RFC defines:

  • How to construct a Hash Time Lock Contract (HTLC) to lock an ERC20 token asset on the Ethereum blockchain.

  • How to deploy, redeem and refund the HTLC during the execution phase of RFC003.

2. Hash Time Lock Contract

2.1. Hash Functions

SHA-256 is the only value the hash_function parameter to comit-rfc-003 may take if this HTLC is used. Future RFCs may make modifications to the HTLC to allow other hash functions.

2.2. Parameters

The parameters for the ERC20 HTLC follow RFC003 and are described concretely in the following table:

Variable Description

asset

The quantity of the token to be locked in the HTLC AND the address of the token contract

secret_hash

The hash of the secret (32 bytes for SHA-256)

redeem_identity

The address of the redeeming party

refund_identity

The address of the refunding party

expiry

The absolute UNIX timestamp in seconds after which the HTLC can be refunded

We will refer to the token contract address and quantity from the RFC008 ERC20 asset definition as token_contract and token_quantity respectively.

2.3. Contract

This RFC defines a single disposable contract as the HTLC.

Note that the funding of the HTLC is a two step process:

  • The funder deploys the HTLC contract

  • The funder transfers tokens to the HTLC contract

It is designed like this because it is impossible to deploy an HTLC and transfer a ownership of ERC20 tokens to that HTLC in one transaction.

The contract uses the ERC20 transfer call to transfer ownership of the tokens

The contract never checks the caller so the redeem and refund transactions can be sent by anyone. The token will only ever be transferred to the redeem_identity or the refund_identity.

The contract is compiled from the following EVM assembly with placeholder values marked by <…​>:

{
    // Load received secret size
    calldatasize

    // Check if secret is zero length
    iszero

    // If secret is zero length, jump to branch that checks if expiry time has been reached
    check_expiry
    jumpi

    // Load expected secret size
    32

    // Load received secret size
    calldatasize

    // Compare secret size
    eq
    iszero

    // If passed secret is wrong size, jump to exit contract
    invalid_secret
    jumpi

    // Load secret into memory
    calldatacopy(0, 0, 32)

    // Hash secret with SHA-256 (pre-compiled contract 0x02)
    call(72, 0x02, 0, 0, 32, 33, 32)

    // Placeholder for correct secret hash
    0x1000000000000000000000000000000000000000000000000000000000000001

    // Load hashed secret from memory
    mload(33)

    // Compare hashed secret with existing one
    eq

    // Combine `eq` result with `call` result
    and

    // Jump to redeem if hashes match
    redeem
    jumpi

    // continue to invalid secret if no match
invalid_secret:
    // return "invalidSecret" = 0x696e76616c69645365637265740000000000000000000000000000000000000000
    mstore(0, "invalidSecret")
    revert(0, 32)

check_expiry:
    // Timestamp of the current block in seconds since the epoch
    timestamp

    // Placeholder for refund timestamp
    <expiry>

    // Compare refund timestamp with current timestamp
    lt

    // Jump to refund if time is expired
    refund
    jumpi

    // return "tooEarly" = 0x746f6f4561726c79000000000000000000000000000000000000000000000000
    mstore(0, "tooEarly")
    revert(0, 32)

redeem:
    // log ascii to hex of "redeemed"
    // 0x72656465656d6564000000000000000000000000000000000000000000000000
    log1(0, 32, "redeemed")
    mstore(32, <redeem_identity>) // redeem address
    finishTransferTokens
    jump

refund:
    // log ascii to hex of "refunded"
    // 0x726566756e646564000000000000000000000000000000000000000000000000
    log1(0, 0, "refunded")
    mstore(32, <refund_identity>) // refund address
    finishTransferTokens
    jump

finishTransferTokens:
    mstore(0, 0xa9059cbb) // first 4bytes of keccak256("transfer(address,uint256)")
    mstore(64, <token_quantity>) // Amount
    call(
      sub(gas,100000),
      <token_contract>, // Token Contract address
      0,  // Ether to transfer
      28, // = 32-4
      68, // = 2*32+4
      96, // return location
      32  // return size
    )
    pop

    selfdestruct(mload(32))
}

The following is the contract deployment code encoded template as hex which will deploy the above contract when the placeholder values are replaced with the parameters.

61018c61000f60003961018c6000f3361561007957602036141561004f57602060006000376020602160206000600060026048f17f100000000000000000000000000000000000000000000000000000000000000160215114166100ae575b7f696e76616c69645365637265740000000000000000000000000000000000000060005260206000fd5b426320000002106100f1577f746f6f4561726c7900000000000000000000000000000000000000000000000060005260206000fd5b7f72656465656d656400000000000000000000000000000000000000000000000060206000a1733000000000000000000000000000000000000003602052610134565b7f726566756e64656400000000000000000000000000000000000000000000000060006000a1734000000000000000000000000000000000000004602052610134565b63a9059cbb6000527f5000000000000000000000000000000000000000000000000000000000000005604052602060606044601c6000736000000000000000000000000000000000000006620186a05a03f150602051ff

To compile the contract code replace the placeholder values with the HTLC paramters at the following offsets:

Name Byte Range Length (bytes)

secret_hash

53..85

32

refund_timestamp

139..143

4

redeem_address

229..249

20

refund_address

296..316

20

amount

333..365

32

token_contract_address

379..399

20

The contract emits two logs:

Topic keccack256(Topic) Data Emitted When

ascii to hex of redeemed

0x72656465656d6564000000000000000000000000000000000000000000000000

secret

The contract is redeemed

ascii to hex of refunded

0x726566756e646564000000000000000000000000000000000000000000000000

-

The contract is refunded

The contract returns two different error values for the failure cases:

Description bytes Emitted When

ascii to hex of invalidSecret

0x696e76616c69645365637265740000000000000000000000000000000000000000

Attempt to redeem with an invalid secret

ascii to hex of tooEarly

0x746f6f4561726c79000000000000000000000000000000000000000000000000

Attempt to refund too early

Implementations SHOULD use the following gas limits on the transactions related to the contract:

Transaction Recommended gas limit

Deployment

167,800

Funding

100,000

Redeem

100,000

Refund

100,000

3. Execution Phase

The following section describes how both parties should interact with the Ethereum blockchain during the RFC003 execution phase.

Note that with the ERC20 HTLC there’s a funding stage after the deployment stage. The funding stage MUST be completed before the redeemer tries to redeem.

3.1. Deployment

At the start of the deployment stage, both parties compile the contract code as described in the previous section. We will call this value contract_code.

To deploy the ERC20 HTLC, the funder must deploy the contract_code. They SHOULD do this by sending a contract deployment transaction to the relevant Ethereum blockchain with contract_code as the data of the transaction.

To be notified of the deployment event, both parties MAY watch the blockchain for a transaction with the contract_code as the data. Upon observing the deployment transaction, both parties SHOULD record the address the contract was deployed to (referred to as htlc_contract from now on).

3.2. Funding

After deploying the contract, the funder must transfer ownership of the token_quantity to the htlc_contract. They MUST do this by calling the transfer(htlc_contract,token_quantity) function on the token_contract. This can be done by sending a transaction to the token_contract with the following data:

a9059cbb <20-byte htlc_contract> <32-byte token_quantity>

To be notified of the funding event, both parties MAY watch the blockchain for token_contract emitting the Transfer(address,address,uint256) log where the second address argument is the htlc_contract. (The keccack256 topic filter for the log is: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef).

The redeemer MUST check the transaction receipt that the amount transferred to token_contract was at least token_quantity.

3.3. Redeem

Before redeeming, the redeemer SHOULD wait until the funding transaction has enough confirmation such that they consider it permanent.

To redeem the HTLC, the redeemer SHOULD send a transaction to the htlc_contract with the data of the transaction set to the secret.

To be notified of the redeem event, both parties SHOULD watch the blockchain for htlc_contract emitting the Redeemed() log. If Ethereum is the beta_ledger, then the funder (Bob) MUST watch for such a log, extract the secret from the transaction receipt and continue the protocol. In this case, Bob MUST NOT watch for a transaction sent to htlc_contract with the secret as data. If he does this, he will miss learning the secret if the contract is redeemed by a call from another contract (rather than from a transaction).

3.4. Refund

To refund the HTLC, the funder MUST send a transaction to htlc_contract with empty data in a block with timestamp greater than expiry.

To be notified of the refund event, both parties SHOULD watch the blockchain for htlc_contract emitting the Refunded() log.

3.5. Failure

A transaction to the HTLC will fail in two cases:

  1. If redeem was attempted with an invalid secret: the contract will return invalidSecret in bytes (0x696e76616c69645365637265740000000000000000000000000000000000000000)

  2. If refund was attempted too early: the contract will return tooEarly in bytes (0x746f6f4561726c79000000000000000000000000000000000000000000000000)

Examples/Test vectors

1. RFC003 SWAP REQUEST

The following shows an RFC003 SWAP REQUEST where the alpha_ledger is Ethereum, the alpha_asset is 1 PAY token (with …​ being used where the value is only relevant for the beta_ledger).

{
  "type": "SWAP",
  "headers": {
    "alpha_ledger": {
      "value": "ethereum",
      "parameters": { "network": "mainnet" }
    },
    "beta_ledger": {...},
    "alpha_asset": {
      "value": "erc20",
      "parameters": {
          "quantity": "1000000000000000000",
          "address": "0xb97048628db6b661d4c2aa833e95dbe1a905b280"
      }

    },
    "beta_asset": {...},
    "protocol": {
        "value" : "comit-rfc-003",
        "parameters" : { "hash_function" : "SHA-256" }
    }
  },
  "body": {
    "aplha_ledger_refund_identity": "0x0f59e9e105be01d5e2206792a267406f255c5ea5",
    "alpha_expiry": 1552263040,
    "secret_hash" : "ac5a18da6431ed256965b873ef49dc15a70a0a66e2d28d0c226b5db040123727",
    "beta_ledger_redeem_identity" : "...",
    "beta_expiry" : ...
  },
}

Note, the pre-image for the secret_hash is adebd583a094215e963ebe4a1474b9bb4bf48167e64f3f474d71e41a75494bbb.

2. RFC003 SWAP RESPONSE

A valid RESPONSE to the above REQUEST could look like:

{
  "status" : "OK00",
  "body": {
     "alpha_ledger_redeem_identity": "0x53fd2cac865d3aa1ad6fbdebaa00802c94239fba",
     "beta_ledger_refund_identity": "..."
  }
}

3. HTLC

The above REQUEST and RESPONSE results in the following parameters to the HTLC:

Parameter value

redeem_identity

0x53fd2cac865d3aa1ad6fbdebaa00802c94239fba

refund_identity

0x0f59e9e105be01d5e2206792a267406f255c5ea5

token_contract

0xb97048628db6b661d4c2aa833e95dbe1a905b280

token_quantity

1000000000000000000

secret_hash

ac5a18da6431ed256965b873ef49dc15a70a0a66e2d28d0c226b5db040123727

expiry

1552263040

Which should compile to the following contract_code:

61018c61000f60003961018c6000f3361561007957602036141561004f57602060006000376020602160206000600060026048f17fac5a18da6431ed256965b873ef49dc15a70a0a66e2d28d0c226b5db04012372760215114166100ae575b7f696e76616c69645365637265740000000000000000000000000000000000000060005260206000fd5b42635c85a780106100f1577f746f6f4561726c7900000000000000000000000000000000000000000000000060005260206000fd5b7f72656465656d656400000000000000000000000000000000000000000000000060206000a17353fd2cac865d3aa1ad6fbdebaa00802c94239fba602052610134565b7f726566756e64656400000000000000000000000000000000000000000000000060006000a1730f59e9e105be01d5e2206792a267406f255c5ea5602052610134565b63a9059cbb6000527f0000000000000000000000000000000000000000000000000de0b6b3a7640000604052602060606044601c600073b97048628db6b661d4c2aa833e95dbe1a905b280620186a05a03f150602051ff