diff --git a/outposts/osmosis/README.md b/outposts/osmosis/README.md index e65c56d..b5844f3 100644 --- a/outposts/osmosis/README.md +++ b/outposts/osmosis/README.md @@ -3,13 +3,13 @@ ## Overview The following is an example Osmosis Outpost smart contract that interacts with the -[Osmosis Cross Chain Swap contract](https://github.com/osmosis-labs/osmosis/tree/be63fb58580b87808f6d7ed9523a43e976899258/cosmwasm/contracts/crosschain-swaps), -and specifically version 1 of the contract for the [**Native to Osmosis Native swap**](https://github.com/osmosis-labs/osmosis/tree/main/cosmwasm/contracts/crosschain-swaps#non-native-to-non-native). +[Osmosis Cross Chain Swap contract](https://github.com/osmosis-labs/osmosis/tree/be63fb58580b87808f6d7ed9523a43e976899258/cosmwasm/contracts/crosschain-swaps). This contract can be deployed as a standalone contract and called -from other contracts or you can include this logic -into your own contract. In this case you can swap Evmos for Osmosis tokens +from other contracts, or you can include this logic +into your own contract. In this case you can swap any non-native Coin to Evmos and receive them back on the Evmos chain. -This functionality is available only for the latest Evmos and Osmosis testnets. + +Future versions of this contract will include swapping Evmos for any non-native Coin like ATOM, INJ, TIA etc. ## How it works @@ -21,29 +21,232 @@ It consists of two separate contracts: - [crosschain_swaps](https://celatone.osmosis.zone/testnet/contracts/osmo1ye7nsslrgwc6ngmav67h26zckg8wjeay4agnlzke66f8apq3ls8sqednc4) - this contract checks for a correct `memo` field and routes to the `swaprouter` contract. - [swaprouter](https://celatone.osmosis.zone/testnet/contracts/osmo1cr8pd93vrw236jqr696p23k0g37dzkegjjf9884023ts48yazxhsj38hlv) - - the swap router performs the actual swap. It requires a configuration for the pools a user wants to use to swap. + the swap router performs the actual swap. It requires a configuration for the pools a user wants to use to swap. The +swap router contract can construct swap paths using the `set_route` transaction. You can compose routes between different +pools in order to get the desired output denom. Below is an example of a route that starts with axlUSDC, swaps it for +Osmosis and then swaps that Osmosis for Evmos using different pools. +```json +{ + "set_route": { + "input_denom": "ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858", + "output_denom": "ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A", + "pool_route": [ + { + "pool_id": "1133", + "token_out_denom": "uosmo" + }, + { + "pool_id": "722", + "token_out_denom": "ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A" + } + ] + } +} +``` + +### Evmos + +On Evmos all you need to do is deploy and call the swapping function on the `XSCOutpost` contract, +the memo field is constructed automatically using the helper function. +You will be sending different denoms and in return receive Evmos or Osmosis tokens. At the bottom of this +document you will find constructed `memos` and the parameters you need to provide to the `swap` function. + + + +## Tested Swap Pairs and `memo` fields + +### ATOM -> EVMOS -## Evmos +- calls - `osmosisSwap` -> `osmosisSwapForward` +- params + - `isNative` - `false` + - `firstChannel` - `channel-3` + - `_firstReceiver` - **YOUR COSMOS HUB ADDRESS** + - `_amount` - **AMOUNT** + - `_baseDenom` - `ibc/A4DB47A9D3CF9A068D454513891B526702455D3EF08FB9EB558C561F9DC2B701` + - `memoParams` - `["ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A","YOUR EVMOS BECH32 ADDRESS","channel-141"]` +- final `memo` +```json +{ + "forward": { + "receiver": "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl", + "port": "transfer", + "channel": "channel-141", + "timeout": "2m", + "retries": 2, + "next": { + "wasm": { + "contract": "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl", + "msg": { + "osmosis_swap": { + "output_denom": "ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A", + "receiver": "YOUR EVMOS RECEIVER", + "slippage": { + "twap": { + "slippage_percentage": "20", + "window_seconds": 30 + } + }, + "on_failed_delivery": "do_nothing" + } + } + } + } + } +} +``` -On Evmos all you need to do is call the swapping function on the `XSCOutpost` contract, the memo field is constructed, -automatically using the helper function. You will be sending `atevmos` and in return receive `osmo` tokens which will have -an ibc voucher denom of: `ibc/95AEB3C077D1E35BA2AA79E338EB5B703C835C804127F7CC4942C8F23F710B26` +### USDC -> EVMOS -## Testnet +- calls - `osmosisSwap` -> `osmosisSwapForward` +- params + - `isNative` - `false` + - `firstChannel` - `channel-64` + - `_firstReceiver` - **YOUR NOBLE ADDRESS** + - `_amount` - **AMOUNT** + - `_baseDenom` - `ibc/35357FE55D81D88054E135529BB2AEB1BB20D207292775A19BD82D83F27BE9B4` + - `memoParams` - `["ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A","YOUR EVMOS BECH32 ADDRESS","channel-1"]` +- final `memo` +```json +{ + "forward": { + "receiver": "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl", + "port": "transfer", + "channel": "channel-1", + "timeout": "2m", + "retries": 2, + "next": { + "wasm": { + "contract": "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl", + "msg": { + "osmosis_swap": { + "output_denom": "ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A", + "receiver": "YOUR EVMOS RECEIVER", + "slippage": { + "twap": { + "slippage_percentage": "20", + "window_seconds": 30 + } + }, + "on_failed_delivery": "do_nothing" + } + } + } + } + } +} +``` -This contract is configured for testnet and would **NOT** work on mainnet without changing the configurations. -The channel id, contract address and pool ids are all testnet specific. -For mainnet this document and the contract will be updated with the correct values. +### INJ -> EVMOS -**NOTE** - Moreover the [evmos/osmo pool 48](https://testnet.osmosis.zone/pool/48) was setup in a way that 1 aevmos = 1 osmo. -On mainnet this will not be the case as it will take into consideration the different token precisions and the swap will be adjusted. -On mainnet: evmos token precision = 18, osmo token precision = 6. +- calls - `osmosisSwap` -> `osmosisSwapForward` +- params + - `isNative` - `false` + - `firstChannel` - `channel-10` + - `_firstReceiver` - **YOUR INJECTIVE ADDRESS** + - `_amount` - **AMOUNT** + - `_baseDenom` - `ibc/ADF401C952ADD9EE232D52C8303B8BE17FE7953C8D420F20769AF77240BD0C58` + - `memoParams` - `["ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A","YOUR EVMOS BECH32 ADDRESS","channel-8"]` +- final `memo` +```json +{ + "forward": { + "receiver": "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl", + "port": "transfer", + "channel": "channel-8", + "timeout": "2m", + "retries": 2, + "next": { + "wasm": { + "contract": "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl", + "msg": { + "osmosis_swap": { + "output_denom": "ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A", + "receiver": "YOUR EVMOS RECEIVER", + "slippage": { + "twap": { + "slippage_percentage": "20", + "window_seconds": 30 + } + }, + "on_failed_delivery": "do_nothing" + } + } + } + } + } +} +``` -## Mainnet +### STARS -> EVMOS -TODO +- calls - `osmosisSwap` -> `osmosisSwapForward` +- params + - `isNative` - `false` + - `firstChannel` - `channel-13` + - `_firstReceiver` - **YOUR STARGAZE ADDRESS** + - `_amount` - **AMOUNT** + - `_baseDenom` - `ibc/7564B7F838579DD4517A225978C623504F852A6D0FF7984AFB28F10D36022BE8` + - `memoParams` - `["ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A","YOUR EVMOS BECH32 ADDRESS","channel-0"]` +- final `memo` +```json +{ + "forward": { + "receiver": "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl", + "port": "transfer", + "channel": "channel-0", + "timeout": "2m", + "retries": 2, + "next": { + "wasm": { + "contract": "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl", + "msg": { + "osmosis_swap": { + "output_denom": "ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A", + "receiver": "YOUR EVMOS RECEIVER", + "slippage": { + "twap": { + "slippage_percentage": "20", + "window_seconds": 30 + } + }, + "on_failed_delivery": "do_nothing" + } + } + } + } + } +} +``` -## Disclaimer +### NEOK -> EVMOS -This contract is not yet audited and is considered experimental. -Use at your own risk. +- calls - `osmosisSwap` -> `osmosisSwapNative` +- params + - `isNative` - `true` + - `firstChannel` - `channel-0` + - `_firstReceiver` - `` + - `_amount` - **AMOUNT** + - `_baseDenom` - `erc20/0x655ecB57432CC1370f65e5dc2309588b71b473A9` + - `memoParams` - `["ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A","YOUR EVMOS BECH32 ADDRESS", ""]` +- final `memo` +```json +{ + "wasm": { + "contract": "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl", + "msg": { + "osmosis_swap": { + "output_denom": "ibc/6AE98883D4D5D5FF9E50D7130F1305DA2FFA0C652D1DD9C123657C6B4EB2DF8A", + "receiver": "YOUR EVMOS RECEIVER", + "slippage": { + "twap": { + "slippage_percentage": "20", + "window_seconds": 30 + } + }, + "on_failed_delivery": "do_nothing" + } + } + } +} +``` \ No newline at end of file diff --git a/outposts/osmosis/XCSOutpost.sol b/outposts/osmosis/XCSOutpost.sol index 2ec0f10..bd06472 100644 --- a/outposts/osmosis/XCSOutpost.sol +++ b/outposts/osmosis/XCSOutpost.sol @@ -4,19 +4,24 @@ pragma solidity >=0.8.17; import ".../../../precompiles/stateful/ICS20.sol"; import "../../precompiles/common/Types.sol"; - contract XCSOutpost { + // @dev struct to encapsulate memo parameters + struct MemoParams { + string outputDenom; + string receiver; + string forwardChannel; + } + /// @dev The constants for channel, port and base denom - string private channel = "channel-215"; string private port = "transfer"; - string private XCS_CONTRACT = "osmo1a34wxsxjwvtz3ua4hnkh4lv3d4qrgry0fhkasppplphwu5k538tqcyms9x"; + string private XCS_CONTRACT = "osmo14f7h97tyavuqqnu68ftm6u7xlkvr2ex58f8pjjj27f6saj5en7jsj9u3nl"; // The XCS contract /// @dev Default allowed list is empty indicating no restrictions string[] private defaultAllowList = new string[](0); /// @dev Creates an approval allocation against the smart contract for IBC transfers. - function _approveTransfer(uint256 _amount, string calldata _baseDenom) private { + function _approveTransfer(uint256 _amount, string calldata channel, string calldata _baseDenom) private { // Create the spend limit of coins, in this case only aevmos Coin[] memory spendLimit = new Coin[](1); spendLimit[0] = Coin(_baseDenom, _amount); @@ -30,52 +35,84 @@ contract XCSOutpost { } - /// @dev Preparation of memo field for cross chain swap for case 4 - Native (atevmos) to Osmosis Native (uosmo) - /// @param _output_denom The target denomination to be swapped for, e.g. "uosmo" - /// @param _receiver The address bech32 string receiving the swapped funds - function nativeToOsmoNativeMemo(string memory _output_denom, string memory _receiver) public view returns (string memory) { + /// @dev Preparation of memo field for cross chain swap by first sending the Coin to unwrap in it's native chain. + function osmosisSwapForward(MemoParams calldata memoParams) public view returns (string memory) { + string memory memo = string(abi.encodePacked( + '{', + '"forward": {', + '"receiver": "', XCS_CONTRACT, '",', + '"port": "transfer",', + '"channel": "', memoParams.forwardChannel, '",', + '"timeout": "2m",', + '"retries": 2,', + '"next": {', + '"wasm": {', + '"contract": "', XCS_CONTRACT, '",', + '"msg": {', + '"osmosis_swap": {', + '"output_denom": "', memoParams.outputDenom, '",', + '"receiver": "', memoParams.receiver, '",', + '"slippage": {', + '"twap": {', + '"slippage_percentage": "20",', + '"window_seconds": 30', + '}', + '},', + '"on_failed_delivery": "do_nothing"', + '}', + '}', + '}', + '}', + '}', + '}' + )); + return memo; + } + + /// @dev Swaps native tokens coming from Evmos for Evmos. This includes any ERC20s that are registered as IBC Coins. + function osmosisSwapNative(MemoParams calldata memoParams) public view returns (string memory) { string memory memo = string(abi.encodePacked( '{', '"wasm": {', '"contract": "', XCS_CONTRACT, '",', '"msg": {', '"osmosis_swap": {', - '"output_denom": "', _output_denom, '",', + '"output_denom": "', memoParams.outputDenom, '",', + '"receiver": "', memoParams.receiver, '",', '"slippage": {', '"twap": {', - '"slippage_percentage": "10",', + '"slippage_percentage": "20",', '"window_seconds": 30', - '}', + '}', '},', - '"receiver": "', _receiver, '",', '"on_failed_delivery": "do_nothing"', '}', '}', '}', '}' )); - return memo; - } - // @dev The main Swap function which will swap a base denom for an output denom using the native to osmosis native case - // @param _amount The amount of the base denomination to be swapped - // @param _baseDenom The base denomination used for the swap, e.g. "atevmos" for Evmos testnet - // @param _outputDenom The target denomination to be swapped for, e.g. "uosmo" - // @param _receiver The address bech32 string receiving the swapped funds - function osmosisSwap(uint256 _amount, string calldata _baseDenom, string calldata _outputDenom, string calldata _receiver) public { - _approveTransfer(_amount, _baseDenom); + // @dev The main Swap function which will swap a base denom for an output denom. + function osmosisSwap(bool isNative, string calldata firstChannel, string memory _firstReceiver, uint256 _amount, string calldata _baseDenom, MemoParams calldata memoParams) public { + _approveTransfer(_amount, firstChannel, _baseDenom); Height memory timeoutHeight = Height(100,100); - string memory memo = nativeToOsmoNativeMemo(_outputDenom, _receiver); + + string memory memo; + if (isNative) { + memo = osmosisSwapNative(memoParams); + } else { + memo = osmosisSwapForward(memoParams); + } ICS20_CONTRACT.transfer( port, - channel, + firstChannel, _baseDenom, _amount, msg.sender, - XCS_CONTRACT, // The cross chain swaps CosmWasm contract + _firstReceiver, // The cross chain swaps CosmWasm contract timeoutHeight, 0, memo