Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ICTT send-and-call course #57

Merged
merged 6 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: Introduction
description: Learn how to call another contract function after send tokens to another L1s.
updated: 2024-08-23
authors: [0xstt]
icon: Book
---

In addition to supporting basic token transfers, the token transferrer contracts offer a `sendAndCall` interface for bridging tokens and using them in a smart contract interaction all within a single Interchain Messaging message. If the call to the recipient smart contract fails, the transferred tokens are sent to a fallback recipient address on the destination chain of the transfer. The `sendAndCall` interface enables the direct use of transferred tokens in dApps on other chains, such as performing swaps, using the tokens to pay for fees when invoking services, etc.

Teleporter Messenger has the ability to receive cross-chain messages on the destination chain and casts related messages to the `TeleporterMessage` struct. It then sends these messages to the Home/Remote Transferrer contract, and handles them as `SEND` or `CALL`, as implemented in `TokenHome.sol` or `TokenRemote.sol`.

In this section we will cover the usage of the `CALL` message type with an example implementation.

When `sendAndCall` function is triggered, the following actions are taken;

- The Transferrer Contract grants an allowance to spend tokens on the destination contract.
- The Transferrer Contract encodes the received message as parameters for the `receiveToken` function, as defined in the `IERC20SendAndCallReceiver` or `INativeSendAndCallReceiver` interface.
- The Transferrer Contract checks whether the destination contract's function execution is successfull.
- The Transferrer Contract retrieves the remaining allowance to check if there are any unspent tokens exists.
- The Transferrer Contract removes the allowance for the destination contract.
- The Transferrer Contract sends the remaining tokens to the fallback recipient. If the destination contract fails to execute the function, the full amount will be sent to the fallback recipient.

## Prerequisites

The following prerequisites were covered in previous sections, so you should have already deployed the following contracts before starting this chapter:

- Base ERC20 Token on L1
- Home Transferrer Contract on L1
- Remote Transferrer Contract on Fuji
- A Running AWM-relayer from your L1 to Fuji
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: Send and Call Receivers
description: Learn how tokens are received by the receivers.
updated: 2024-08-23
authors: [0xstt]
icon: BookOpen
---

The interfaces `IERC20SendAndCallReceiver` or `INativeSendAndCallReceiver` are used for contracts that handle receiving ERC20 or native tokens of the Interchain Token Transfer protocol. They are similar to the `ITeleporterReceiver` interface, but they are specifically designed to handle token transfers.

### IERC20SendAndCallReceiver

The `receiveTokens` function will be called by the Transferrer bridge contract. The contract must implement this function to receive the tokens and can retrieve all the information about the origin of the tokens, the token, the bridge used, and the amount of tokens transferred from the parameters.

```solidity title="lib/avalanche-interchain-token-transfer/contracts/src/interfaces/IERC20SendAndCallReceiver.sol"
/**
* @notice Interface for contracts that are called to receive token transfers.
*/
interface IERC20SendAndCallReceiver {
/**
* @notice Called to receive the amount of the given token
* @param sourceBlockchainID Blockchain ID that the transfer originated from
* @param originTokenTransferrerAddress Address of the token transferrer that initiated the Teleporter message
* @param originSenderAddress Address of the sender that sent the transfer. This value
* should only be trusted if {originTokenTransferrerAddress} is verified and known.
* @param token Address of the token to be received
* @param amount Amount of the token to be received
* @param payload Arbitrary data provided by the caller
*/
function receiveTokens(
bytes32 sourceBlockchainID,
address originTokenTransferrerAddress,
address originSenderAddress,
address token,
uint256 amount,
bytes calldata payload
) external;
}
```

### INativeSendAndCallReceiver

The `INativeSendAndCallReceiver` interface is used for contracts that handle receiving native tokens of the Interchain Token Transfer protocol. It is similar to the `IERC20SendAndCallReceiver` interface, but does not include the `token` and `amount` parameters. The `receiveTokens` is now `payable`. There is only a single native token on each chain, so the address is not needed. The amount can be determined from calling `msg.value`.

```solidity title="lib/avalanche-interchain-token-transfer/contracts/src/interfaces/INativeSendAndCallReceiver.sol"
/**
* @notice Interface for a contracts that are called to receive native tokens.
*/
interface INativeSendAndCallReceiver {
/**
* @notice Called to receive the amount of the native token. Implementations
* must properly handle the msg.value of the call in order to ensure it doesn't
* become improperly made inaccessible.
* @param sourceBlockchainID Blockchain ID that the transfer originated from
* @param originTokenTransferrerAddress Address of the token transferrer that initiated the Teleporter message
* @param originSenderAddress Address of the sender that sent the transfer. This value
* should only be trusted if {originTokenTransferrerAddress} is verified and known.
* @param payload Arbitrary data provided by the caller
*/
function receiveTokens(
bytes32 sourceBlockchainID,
address originTokenTransferrerAddress,
address originSenderAddress,
bytes calldata payload
) external payable;
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: Mock Receivers
description: A brief overview of the mock ERC20 and native token receivers used for testing in sendAndCall functions.
updated: 2024-08-23
authors: [0xstt]
icon: BookOpen
---

The primary purpose of these mock contracts is to test cross-chain token transfers and execute contract logic. These contracts implement the `IERC20SendAndCallReceiver` and `INativeSendAndCallReceiver` interfaces to handle token transfers, either for ERC20 tokens or native tokens, across Avalanche L1s.

This contract only performs the following actions:

- Checks if the message was received from a blocked sender.
- Emits a TokensReceived event.
- Checks if the payload is empty.
- Receives tokens to itself.

```solidity title="lib/avalanche-interchain-token-transfer/contracts/src/mocks/MockERC20SendAndCallReceiver.sol"
pragma solidity 0.8.25;

import {IERC20SendAndCallReceiver} from "../interfaces/IERC20SendAndCallReceiver.sol";
import {SafeERC20TransferFrom} from "../utils/SafeERC20TransferFrom.sol";
import {SafeERC20} from "@openzeppelin/[email protected]/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/[email protected]/token/ERC20/IERC20.sol";
import {Context} from "@openzeppelin/[email protected]/utils/Context.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

/**
* @notice This is mock implementation of {receiveTokens} to be used in tests.
* This contract DOES NOT provide a mechanism for accessing the tokens transfered to it.
* Real implementations must ensure that tokens are properly handled and not incorrectly locked.
*/
contract MockERC20SendAndCallReceiver is Context, IERC20SendAndCallReceiver {
using SafeERC20 for IERC20;

mapping(bytes32 blockchainID => mapping(address senderAddress => bool blocked)) public
blockedSenders;

/**
* @dev Emitted when receiveTokens is called.
*/
event TokensReceived(
bytes32 indexed sourceBlockchainID,
address indexed originTokenTransferrerAddress,
address indexed originSenderAddress,
address token,
uint256 amount,
bytes payload
);

/** // [!code highlight:28]
* @dev See {IERC20SendAndCallReceiver-receiveTokens}
*/
function receiveTokens(
bytes32 sourceBlockchainID,
address originTokenTransferrerAddress,
address originSenderAddress,
address token,
uint256 amount,
bytes calldata payload
) external {
require(
!blockedSenders[sourceBlockchainID][originSenderAddress],
"MockERC20SendAndCallReceiver: sender blocked"
);
emit TokensReceived({
sourceBlockchainID: sourceBlockchainID,
originTokenTransferrerAddress: originTokenTransferrerAddress,
originSenderAddress: originSenderAddress,
token: token,
amount: amount,
payload: payload
});

require(payload.length > 0, "MockERC20SendAndCallReceiver: empty payload");

SafeERC20TransferFrom.safeTransferFrom(IERC20(token), _msgSender(), amount);
}

/**
* @notice Block a sender from sending tokens to this contract.
* @param blockchainID The blockchain ID of the sender.
* @param senderAddress The address of the sender.
*/
function blockSender(bytes32 blockchainID, address senderAddress) external {
blockedSenders[blockchainID][senderAddress] = true;
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Deploy a Mock Receiver
description: Learn how to deploy a mock receiver contract.
updated: 2024-08-23
authors: [0xstt]
icon: Terminal
---
import { Step, Steps } from 'fumadocs-ui/components/steps';

In this section, you will deploy mock receiver contracts on the Avalanche L1.

<Steps>
<Step>

### Receiver Deployment

You can choose to deploy either the `MockERC20SendAndCallReceiver` or the `MockNativeSendAndCallReceiver` contract depending your token type.

```bash
forge create --rpc-url myblockchain --private-key $PK lib/avalanche-interchain-token-transfer/contracts/src/mocks/MockERC20SendAndCallReceiver.sol:MockERC20SendAndCallReceiver
```

</Step>
<Step>

### Save Receiver Address

After deployment, save the `Deployed to` address in an environment variable for future use.

```bash
export MOCK_RECEIVER_ADDRESS=<address>
```

</Step>
<Step>

### Send Tokens

Use the following command to send tokens to the mock receiver contract:

```bash
cast send --rpc-url myblockchain --private-key $PK $ERC20_HOME_C_CHAIN \
"sendAndCall((bytes32, address, address, bytes, uint256, uint256, address, address, address, uint256, uint256), uint256)" \
"(${C_CHAIN_BLOCKCHAIN_ID_HEX}, ${ERC20_TOKEN_REMOTE_L1}, ${MOCK_RECEIVER_ADDRESS}, 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 2500000, 2000000, 0x0000000000000000000000000000000000000000, ${FUNDED_ADDRESS}, ${ERC20_HOME_C_CHAIN}, 0, 0)" 100000000000000000000
```

</Step>
<Step>

### Verify the Results

Check the logs and emitted events to verify that the tokens were received correctly.

TBD: Provide instructions

</Step>
</Steps>

After successfully deploying the contract, move on to testing the mock receivers.

This file was deleted.

Loading
Loading