StargateComposer.sol

Wrapper Contract that wraps IStargateRouter to add additional functionality to Stargate Composed calls.

swap()

This code snippet shows how the StargateComposer.sol uses the IStargateRouter to swap tokens using Stargate to another chain.

/**
 * @param _dstChainId - destination chain identifier
 * @param _srcPoolId - source pool identifier
 * @param _dstPoolId - destination pool identifier
 * @param _refundAddress - refund address
 * @param _amountLD - amount (local decimals) to swap on source
 * @param _minAmountLD - min amount (local decimals) to receive on destination
 * @param _lzTxParams - struct: dstGasForCall,dstNativeAmount,dstNativeAddr
 * @param _to - destination address (the sgReceive() implementer)
 * @param _payload - bytes payload
 */
function swap(
    uint16 _dstChainId,
    uint256 _srcPoolId,
    uint256 _dstPoolId,
    address payable _refundAddress,
    uint256 _amountLD,
    uint256 _minAmountLD,
    IStargateRouter.lzTxObj memory _lzTxParams,
    bytes calldata _to,
    bytes calldata _payload
) external override payable nonSwapReentrant {
    bytes memory newPayload;
    bytes memory peer;
    if(_payload.length > 0) {
        newPayload = _buildPayload(_to, _payload);
        peer = _getPeer(_dstChainId);

        // overhead for calling composer's sgReceive()
        _lzTxParams.dstGasForCall += dstGasReserve + transferOverhead;
    } else {
        newPayload = "";
        peer = _to;
    }

    if(isEthPool(_srcPoolId)) {
        require(msg.value > _amountLD, "Stargate: msg.value must be > _swapAmount.amountLD");
        IStargateEthVault(stargateEthVaults[_srcPoolId]).deposit{value: _amountLD}();
        IStargateEthVault(stargateEthVaults[_srcPoolId]).approve(address(stargateRouter), _amountLD);
    } else {
        PoolInfo memory poolInfo = _getPoolInfo(_srcPoolId);
        // remove dust
        if (poolInfo.convertRate > 1) _amountLD = _amountLD.div(poolInfo.convertRate).mul(poolInfo.convertRate);
        // transfer token to this contract
        IERC20(poolInfo.token).safeTransferFrom(msg.sender, address(this), _amountLD);
    }

    stargateRouter.swap{value: isEthPool(_srcPoolId) ? msg.value - _amountLD : msg.value}(
        _dstChainId,
        _srcPoolId,
        _dstPoolId,
        _refundAddress,
        _amountLD,
        _minAmountLD,
        _lzTxParams,
        peer, // swap the to address with the peer address
        newPayload
    );
}

buildPayload()

In the swap function it calls buildPayload to include the msg.sender in the payload.

 function _buildPayload(
    bytes calldata _to,
    bytes calldata _payload
) internal view returns (bytes memory) {
    require(_to.length == 20, "Stargate: invalid to address");

    // new payload = to(20) + sender(20) + payload
    // encoding the sender allows the receiver to know who called the Stargate
    return abi.encodePacked(_to, msg.sender, _payload);
}

sgReceive()

StargateComposer.sol implements IStargateReceiver so it can implement the sgReceive function and receive the tokens and payload from the IStargateRouter on the destination chain. It then forwards the sgReceive call to the intended receiver with the original msg.sender who initialed the swap on source.

/**
 * @param _srcChainId - source chain identifier
 * @param _srcAddress - source address identifier
 * @param _nonce - message ordering nonce
 * @param _token - token contract
 * @param _amountLD - amount (local decimals) to recieve 
 * @param _payload - bytes containing the toAddress
 */
function sgReceive(
    uint16 _srcChainId,
    bytes memory _srcAddress,
    uint256 _nonce,
    address _token,
    uint256 _amountLD,
    bytes memory _payload
) external override {
    require(msg.sender == address(stargateRouter), "Stargate: only router");
    // will just ignore the payload in some invalid configuration
    if (_payload.length <= 40) return; // 20 + 20 + payload

    address intendedReceiver = _payload.toAddress(0);

    (bool success, bytes memory data) = _token.call(abi.encodeWithSelector(SELECTOR, intendedReceiver, _amountLD));
    if (success && (data.length == 0 || abi.decode(data, (bool)))) {
        if (!intendedReceiver.isContract()) return; // ignore

        bytes memory callData = abi.encodeWithSelector(
            IStargateReceiver.sgReceive.selector,
            _srcChainId,
            abi.encodePacked(_payload.toAddress(20)), // use the caller as the srcAddress (the msg.sender caller the StargateComposer at the source)
            _nonce,
            _token,
            _amountLD,
            _payload.slice(40, _payload.length - 40)
        );

        // no point in requires, because it will revert regardless
        uint256 externalGas = gasleft() - dstGasReserve;

        (bool safeCallSuccess, bytes memory reason) = intendedReceiver.safeCall(externalGas, 0, 150, callData); // only return 150 bytes of data

        if (!safeCallSuccess) {
            payloadHashes[_srcChainId][_srcAddress][_nonce] = keccak256(abi.encodePacked(intendedReceiver, callData));
            emit CachedSwapSaved(_srcChainId, _srcAddress, _nonce, reason);
        }

    } else {
        // do nothing, token swap failed and can't be delivered, tokens are held inside this contract
        emit ComposedTokenTransferFailed(_token, intendedReceiver, _amountLD);
    }
}

Last updated