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