├── .dockerignore ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ ├── check-generated.yml │ ├── codeql.yml │ ├── interchaintest.yaml │ ├── lint.yml │ ├── push_docker_image.yml │ └── tests.yml ├── .gitignore ├── Dockerfile ├── Makefile ├── README-v2.md ├── README.md ├── ante ├── ante.go ├── ante_test.go ├── fee.go └── fee_test.go ├── app ├── README.md ├── ante.go ├── app.go ├── const.go ├── encoding.go ├── export.go ├── genesis.go ├── genesis_account.go ├── helpers │ └── test_helpers.go ├── params │ ├── config.go │ ├── doc.go │ ├── encoding.go │ ├── params.go │ ├── proto.go │ └── weights.go ├── sim_test.go ├── test_access.go └── test_helpers.go ├── cmd └── feeappd │ ├── cmd │ ├── genaccounts.go │ ├── root.go │ ├── root_test.go │ └── testnet.go │ └── main.go ├── go.mod ├── go.sum ├── go.work.example ├── proto ├── Dockerfile ├── buf.gen.gogo.yaml ├── buf.lock ├── buf.yaml └── feeabstraction │ └── absfee │ └── v1beta1 │ ├── epoch.proto │ ├── feepool.proto │ ├── genesis.proto │ ├── osmosisibc.proto │ ├── params.proto │ ├── proposal.proto │ ├── query.proto │ └── tx.proto ├── sample_pool.json ├── scripts ├── README.md ├── bytecode │ ├── crosschain_registry.wasm │ ├── crosschain_swaps.wasm │ ├── fee_abstraction.wasm │ ├── ibc_stargate-aarch64.wasm │ ├── ibc_stargate.wasm │ ├── ibc_stargate2.wasm │ └── swaprouter.wasm ├── check-generated.sh ├── deploy_and_channel.sh ├── deploy_local.sh ├── fee_abstraction.wasm ├── host_zone.json ├── host_zone_gaia.json ├── host_zone_query.json ├── ibc_swap │ ├── setup.sh │ └── test.json ├── node_start │ ├── runnode_custom.sh │ ├── runnode_gaia.sh │ └── runnode_osmosis.sh ├── pool.json ├── proposal.json ├── proposal_query.json ├── protocgen.sh ├── relayer_hermes │ ├── alice.json │ ├── bob.json │ ├── config_feeabs_gaia.toml │ ├── config_feeabs_osmosis.toml │ ├── config_osmosis_gaia.toml │ └── gnad.json ├── run_relayer_feeabs_gaia.sh ├── run_relayer_feeabs_osmo.sh └── run_relayer_osmo_gaia.sh ├── testnode.sh ├── tests └── interchaintest │ ├── bytecode │ ├── crosschain_registry.wasm │ ├── crosschain_swaps.wasm │ └── swaprouter.wasm │ ├── chain_start_test.go │ ├── go.mod │ ├── go.sum │ ├── ibc_transfer_test.go │ ├── osmosistypes │ └── gamm │ │ ├── balancer │ │ ├── balancerPool.pb.go │ │ ├── codec.go │ │ ├── msgs.go │ │ ├── pool.go │ │ └── tx.pb.go │ │ └── types │ │ ├── codec.go │ │ ├── genesis.pb.go │ │ ├── msgs.go │ │ ├── query.pb.go │ │ ├── query.pb.gw.go │ │ └── tx.pb.go │ ├── packet_foward_test.go │ ├── proposal │ ├── host_zone.json │ └── proposal.json │ └── setup.go └── x └── feeabs ├── ante ├── decorate.go └── expected_keepers.go ├── client └── cli │ ├── query.go │ ├── tx.go │ ├── tx_test.go │ └── util.go ├── ibc_module.go ├── ibctesting ├── README.md ├── chain.go ├── coordinator.go ├── endpoint.go ├── event_utils.go ├── path.go └── wasm.go ├── keeper ├── abci.go ├── config.go ├── epoch.go ├── exchange_rate.go ├── genesis.go ├── genesis_test.go ├── grpc_query.go ├── grpc_query_test.go ├── host_zone_test.go ├── ibc.go ├── keeper.go ├── keeper_test.go ├── module.go ├── msgserver.go ├── param_test.go ├── proposal.go ├── proposal_test.go └── swap.go ├── module.go ├── proposal_handler.go ├── relay_test.go ├── spec ├── 01_concepts.md ├── 02_state.md ├── 03_epoch.md ├── 04_events.md ├── 05_params.md ├── 06_gov.md ├── 07_ibc.md ├── Integration.md ├── README.md └── kelpr_testnet.json └── types ├── build_memo.go ├── build_memo_test.go ├── codec.go ├── codec_test.go ├── epoch.go ├── epoch.pb.go ├── errors.go ├── events.go ├── expected_keepers.go ├── feepool.pb.go ├── genesis.go ├── genesis.pb.go ├── ibc.go ├── keys.go ├── msg.go ├── osmosisibc.pb.go ├── params.go ├── params.pb.go ├── pool.go ├── proposal.go ├── proposal.pb.go ├── query.pb.go ├── query.pb.gw.go └── tx.pb.go /.dockerignore: -------------------------------------------------------------------------------- 1 | assets/ 2 | build/ 3 | docs/ 4 | networks/ 5 | proto/ 6 | scripts/ 7 | tools/ 8 | .github/ 9 | .git/ 10 | .vscode/ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/.gitattributes -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - main 6 | 7 | name: Build 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | name: build 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Setup go 16 | uses: actions/setup-go@v3 17 | with: 18 | go-version: 1.19 19 | - run: go build ./... 20 | -------------------------------------------------------------------------------- /.github/workflows/check-generated.yml: -------------------------------------------------------------------------------- 1 | name: Check generated code 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | branches: 6 | - '*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | check-proto: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - 16 | uses: actions/setup-go@v3 17 | with: 18 | go-version: '1.19' 19 | - 20 | uses: actions/checkout@v3 21 | with: 22 | fetch-depth: 1 # we need a .git directory to run git diff 23 | - 24 | name: "Check protobuf generated code" 25 | run: scripts/check-generated.sh -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "**.go" 7 | push: 8 | branches: 9 | - main 10 | - release/** 11 | paths: 12 | - "**.go" 13 | 14 | jobs: 15 | analyze: 16 | name: Analyze 17 | runs-on: ubuntu-latest 18 | permissions: 19 | actions: read 20 | contents: read 21 | security-events: write 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v3 26 | - uses: actions/setup-go@v3 27 | with: 28 | go-version: 1.19 29 | # Initializes the CodeQL tools for scanning. 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v2 32 | with: 33 | languages: "go" 34 | queries: crypto-com/cosmos-sdk-codeql@main,security-and-quality 35 | 36 | - name: Build 37 | run: make build 38 | 39 | - name: Perform CodeQL Analysis 40 | uses: github/codeql-action/analyze@v2 -------------------------------------------------------------------------------- /.github/workflows/interchaintest.yaml: -------------------------------------------------------------------------------- 1 | name: Interchain Tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | build-and-push-image: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - 14 | name: Check out the repo 15 | uses: actions/checkout@v3 16 | - 17 | name: Set up QEMU 18 | uses: docker/setup-qemu-action@v2 19 | - 20 | name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v2 22 | - name: Login to GitHub Container Registry 23 | uses: docker/login-action@v2 24 | with: 25 | registry: ghcr.io 26 | username: ${{ github.repository_owner }} 27 | password: ${{ secrets.GITHUB_TOKEN }} 28 | - 29 | name: Build and push 30 | id: build_push_image 31 | uses: docker/build-push-action@v3 32 | with: 33 | file: Dockerfile 34 | context: . 35 | push: true 36 | platforms: linux/amd64,linux/arm64 37 | tags: | 38 | ghcr.io/notional-labs/fee-abstraction-ictest:latest 39 | test-basic: 40 | runs-on: ubuntu-latest 41 | needs: build-and-push-image 42 | steps: 43 | - name: Set up Go 1.19 44 | uses: actions/setup-go@v3 45 | with: 46 | go-version: 1.19 47 | 48 | - name: checkout code 49 | uses: actions/checkout@v3 50 | 51 | - run: make ictest-basic 52 | env: 53 | BRANCH_CI: "latest" 54 | 55 | test-ibc: 56 | runs-on: ubuntu-latest 57 | needs: build-and-push-image 58 | steps: 59 | - name: Set up Go 1.19 60 | uses: actions/setup-go@v3 61 | with: 62 | go-version: 1.19 63 | 64 | - name: checkout code 65 | uses: actions/checkout@v3 66 | 67 | - run: make ictest-ibc 68 | env: 69 | BRANCH_CI: "latest" 70 | 71 | test-packet-forward: 72 | runs-on: ubuntu-latest 73 | needs: build-and-push-image 74 | steps: 75 | - name: Set up Go 1.19 76 | uses: actions/setup-go@v3 77 | with: 78 | go-version: 1.19 79 | 80 | - name: checkout code 81 | uses: actions/checkout@v3 82 | 83 | - run: make ictest-packet-forward 84 | env: 85 | BRANCH_CI: "latest" 86 | 87 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | permissions: 10 | contents: read 11 | jobs: 12 | golangci: 13 | name: lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/setup-go@v3 17 | with: 18 | go-version: 1.19 19 | - uses: actions/checkout@v3 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v3 22 | with: 23 | version: latest 24 | args: --timeout 15m -------------------------------------------------------------------------------- /.github/workflows/push_docker_image.yml: -------------------------------------------------------------------------------- 1 | # This workflow pushes new fee-abstraction docker images on every new tag. 2 | # 3 | # On every new `vX.Y.Z` tag the following images are pushed: 4 | # 5 | # notional-labs/fee-abstraction:X.Y.Z # is pushed 6 | # notional-labs/fee-abstraction:X.Y # is updated to X.Y.Z 7 | # notional-labs/fee-abstraction:X # is updated to X.Y.Z 8 | # notional-labs/fee-abstraction:latest # is updated to X.Y.Z 9 | # 10 | # All the images above have support for linux/amd64 and linux/arm64. 11 | # 12 | # Due to QEMU virtualization used to build multi-platform docker images 13 | # this workflow might take a while to complete. 14 | 15 | name: Push Docker Images 16 | 17 | on: 18 | release: 19 | types: [published, created, edited] 20 | push: 21 | tags: 22 | - 'v[0-9]+.[0-9]+.[0-9]+' # ignore rc 23 | jobs: 24 | feeapp-images: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - 28 | name: Check out the repo 29 | uses: actions/checkout@v3 30 | - 31 | name: Set up QEMU 32 | uses: docker/setup-qemu-action@v2 33 | - 34 | name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@v2 36 | - name: Login to GitHub Container Registry 37 | uses: docker/login-action@v2 38 | with: 39 | registry: ghcr.io 40 | username: ${{ github.repository_owner }} 41 | password: ${{ secrets.GITHUB_TOKEN }} 42 | - 43 | name: Parse tag 44 | id: tag 45 | run: | 46 | VERSION=$(echo ${{ github.ref_name }} | sed "s/v//") 47 | MAJOR_VERSION=$(echo $VERSION | cut -d '.' -f 1) 48 | MINOR_VERSION=$(echo $VERSION | cut -d '.' -f 2) 49 | PATCH_VERSION=$(echo $VERSION | cut -d '.' -f 3) 50 | echo "VERSION=$VERSION" >> $GITHUB_ENV 51 | echo "MAJOR_VERSION=$MAJOR_VERSION" >> $GITHUB_ENV 52 | echo "MINOR_VERSION=$MINOR_VERSION" >> $GITHUB_ENV 53 | echo "PATCH_VERSION=$PATCH_VERSION" >> $GITHUB_ENV 54 | - 55 | name: Build and push 56 | id: build_push_image 57 | uses: docker/build-push-action@v3 58 | with: 59 | file: Dockerfile 60 | context: . 61 | push: true 62 | platforms: linux/amd64,linux/arm64 63 | tags: | 64 | ghcr.io/notional-labs/fee-abstraction:${{ env.MAJOR_VERSION }} 65 | ghcr.io/notional-labs/fee-abstraction:${{ env.MAJOR_VERSION }}.${{ env.MINOR_VERSION }} 66 | ghcr.io/notional-labs/fee-abstraction:${{ env.MAJOR_VERSION }}.${{ env.MINOR_VERSION }}.${{ env.PATCH_VERSION }} -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - main 6 | 7 | name: Tests 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | name: test 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v3 16 | with: 17 | go-version: 1.19 18 | - name: Checkout code 19 | uses: actions/checkout@v3 20 | - name: Test 21 | run: go test ./... 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | # Go 17 | go.work 18 | go.work.sum -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | ARG GO_VERSION="1.19" 4 | ARG RUNNER_IMAGE="gcr.io/distroless/static-debian11" 5 | 6 | # -------------------------------------------------------- 7 | # Builder 8 | # -------------------------------------------------------- 9 | 10 | FROM golang:${GO_VERSION}-alpine as builder 11 | 12 | ARG GIT_VERSION 13 | ARG GIT_COMMIT 14 | 15 | RUN apk add --no-cache \ 16 | ca-certificates \ 17 | build-base \ 18 | linux-headers 19 | 20 | # Download go dependencies 21 | WORKDIR /feeapp 22 | COPY go.mod go.sum ./ 23 | RUN --mount=type=cache,target=/root/.cache/go-build \ 24 | --mount=type=cache,target=/root/go/pkg/mod \ 25 | go mod download 26 | 27 | # Cosmwasm - Download correct libwasmvm version 28 | RUN WASMVM_VERSION=$(go list -m github.com/CosmWasm/wasmvm | cut -d ' ' -f 2) && \ 29 | wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/libwasmvm_muslc.$(uname -m).a \ 30 | -O /lib/libwasmvm_muslc.a && \ 31 | # verify checksum 32 | wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/checksums.txt -O /tmp/checksums.txt && \ 33 | sha256sum /lib/libwasmvm_muslc.a | grep $(cat /tmp/checksums.txt | grep $(uname -m) | cut -d ' ' -f 1) 34 | 35 | # Copy the remaining files 36 | COPY . . 37 | 38 | # Build feeappd binary 39 | RUN --mount=type=cache,target=/root/.cache/go-build \ 40 | --mount=type=cache,target=/root/go/pkg/mod \ 41 | GOWORK=off go build \ 42 | -mod=readonly \ 43 | -tags "netgo,ledger,muslc" \ 44 | -ldflags \ 45 | "-X github.com/cosmos/cosmos-sdk/version.Name="feeapp" \ 46 | -X github.com/cosmos/cosmos-sdk/version.AppName="feeappd" \ 47 | -X github.com/cosmos/cosmos-sdk/version.Version=${GIT_VERSION} \ 48 | -X github.com/cosmos/cosmos-sdk/version.Commit=${GIT_COMMIT} \ 49 | -X github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger,muslc \ 50 | -w -s -linkmode=external -extldflags '-Wl,-z,muldefs -static'" \ 51 | -trimpath \ 52 | -o /feeapp/build/feeappd \ 53 | /feeapp/cmd/feeappd/main.go 54 | 55 | # -------------------------------------------------------- 56 | # Runner 57 | # -------------------------------------------------------- 58 | 59 | FROM ${RUNNER_IMAGE} 60 | 61 | COPY --from=builder /feeapp/build/feeappd /bin/feeappd 62 | 63 | ENV HOME /feeapp 64 | WORKDIR $HOME 65 | 66 | # rest server 67 | EXPOSE 1317 68 | # tendermint p2p 69 | EXPOSE 26656 70 | # tendermint rpc 71 | EXPOSE 26657 72 | # grpc 73 | EXPOSE 9090 74 | 75 | ENTRYPOINT ["feeappd"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fee Abstraction 2 | ## This repo has been moved to https://github.com/osmosis-labs/fee-abstraction 3 | ## Context 4 | 5 | The concrete use cases which motivated this module include: 6 | - The desire to use IBC token as transaction fees on any chain instead of having to use native token as fee. 7 | - To fully take advantage of the newly represented Osmosis [``swap router``](https://github.com/osmosis-labs/osmosis/tree/main/cosmwasm/contracts) with the [``ibc-hooks``](https://github.com/osmosis-labs/osmosis/tree/main/x/ibc-hooks) module and the [``async-icq``](https://github.com/strangelove-ventures/async-icq) module. 8 | 9 | ## Description 10 | 11 | Fee abstraction modules enable users on any Cosmos chain with IBC connections to pay fee using ibc token. 12 | 13 | Fee-abs implementation: 14 | - Fee-abs module imported to the customer chain. 15 | 16 | The implememtation also uses Osmosis swap router and async-icq module which are already deployed on Osmosis testnet. 17 | 18 | ## Prototype 19 | 20 | Fee-abs mechanism in a nutshell: 21 | 1. Pulling `twap data` and update exchange rate: 22 | - Periodically pulling `twap data` from osmosis by ibc-ing to `async-icq` module on Osmosis, this `twap data` will update the exchange rate of osmosis to customer chain's native token. 23 | 2. Handling txs with ibc-token fee: 24 | - The exchange rate is used to calculate the amount of ibc-token needed for tx fee allowing users to pay ibc-token for tx fee instead of chain's native token. 25 | 3. Swap accumulated ibc-token fee: 26 | - The collected ibc-token users use for tx fee is periodically swaped back to customer chain's native token using osmosis. 27 | 28 | We'll goes into all the details now: 29 | 30 | #### Pulling `twap data` and update exchange rate 31 | For this to work, we first has to set up an ibc channel from `feeabs` to `async-icq`. This channel set-up process can be done by anyone, just like setting up an ibc transfer channel. Once that ibc channel is there, we'll use that channel to ibc-query Twap data. Let's call this the querying channel. 32 | 33 | The process of pulling Twap data and update exchange rate : 34 | 35 | ![](https://i.imgur.com/HJ9a26H.png) 36 | 37 | Description : 38 | For every `update exchange rate period`, at fee-abs `BeginBlocker()` we submit a `InterchainQueryPacketData` which wrapped `QueryArithmeticTwapToNowRequest` to the querying channel on the customer chain's end. Then relayers will submit `MsgReceivePacket` so that our `QueryTwapPacket` which will be routed to `async-icq` module to be processed. `async-icq` module then unpack `InterchainQueryPacketData` and send query to TWAP module. The correspone response will be wrapped in the ibc acknowledgement. Relayers then submit `MsgAcknowledgement` to the customer chain so that the ibc acknowledgement is routed to fee-abs to be processed. Fee-abs then update exchange rate according to the Twap wrapped in the ibc acknowledgement. 39 | 40 | #### Handling txs with ibc-token fee 41 | We modified `MempoolFeeDecorator` so that it can handle ibc-token as fee. If the tx has ibc-token fee, the AnteHandler will first check if that token is allowed (which is setup by Gov) we basically replace the amount of ibc-token with the equivalent native-token amount which is calculated by `exchange rate` * `ibc-token amount`. 42 | 43 | We have an account to manage the ibc-token user used to pay for tx fee. The collected ibc-token fee is sent to that account instead of community pool account. 44 | 45 | #### Swap accumulated ibc-tokens fee 46 | Fee-abstraction will use osmosis's Cross chain Swap (XCS) feature to do this. We basically ibc transfer to the osmosis crosschain swap contract with custom memo to swap the osmosis fee back to customer chain's native-token and ibc transfer back to the customer chain. 47 | 48 | ##### How XCS work 49 | ###### Reverse With Path-unwinding to get Ibc-token on Osmosis: 50 | - Create a ibc transfer message with a specific MEMO to work with ibc [``packet-forward-middleware``](https://github.com/strangelove-ventures/packet-forward-middleware) which is path-unwinding (an ibc feature that allow to automatic define the path and ibc transfer multiple hop follow the defined path) 51 | - Ibc transfer the created packet to get the fee Ibc-token on Osmosis 52 | 53 | Ex: When you sent STARS on Hub to Osmosis, you will get Osmosis(Hub(STARS)) which is different with STARS on Osmosis Osmosis(STARS). It will reverse back Osmosis(Hub(STARS)) to Osmosis(STARS): 54 | 55 | ![](https://i.imgur.com/D1wSrMm.png) 56 | 57 | ###### Swap Ibc-token: 58 | After reverse the ibc-token, XCS will : 59 | - Swap with the specific pool (which is defined in the transfer packet from Feeabs-chain) to get Feeabs-chain native-token 60 | - Transfer back Feeabs-chain native-token to Feeabs module account (will use to pay fee for other transaction) 61 | 62 | ![](https://i.imgur.com/YKOK8mr.png) 63 | 64 | Current version of fee-abstraction working with XCSv2 65 | 66 | -------------------------------------------------------------------------------- /ante/ante.go: -------------------------------------------------------------------------------- 1 | package ante 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | "github.com/cosmos/cosmos-sdk/x/auth/ante" 7 | ibcante "github.com/cosmos/ibc-go/v4/modules/core/ante" 8 | ibckeeper "github.com/cosmos/ibc-go/v4/modules/core/keeper" 9 | ) 10 | 11 | // HandlerOptions extend the SDK's AnteHandler options by requiring the IBC 12 | // channel keeper. 13 | type HandlerOptions struct { 14 | ante.HandlerOptions 15 | 16 | IBCkeeper *ibckeeper.Keeper 17 | BypassMinFeeMsgTypes []string 18 | } 19 | 20 | func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) { 21 | if opts.AccountKeeper == nil { 22 | return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") 23 | } 24 | if opts.BankKeeper == nil { 25 | return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") 26 | } 27 | if opts.SignModeHandler == nil { 28 | return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") 29 | } 30 | 31 | sigGasConsumer := opts.SigGasConsumer 32 | if sigGasConsumer == nil { 33 | sigGasConsumer = ante.DefaultSigVerificationGasConsumer 34 | } 35 | 36 | anteDecorators := []sdk.AnteDecorator{ 37 | ante.NewSetUpContextDecorator(), 38 | ante.NewRejectExtensionOptionsDecorator(), 39 | NewMempoolFeeDecorator(opts.BypassMinFeeMsgTypes), 40 | ante.NewValidateBasicDecorator(), 41 | ante.NewTxTimeoutHeightDecorator(), 42 | ante.NewValidateMemoDecorator(opts.AccountKeeper), 43 | ante.NewConsumeGasForTxSizeDecorator(opts.AccountKeeper), 44 | ante.NewDeductFeeDecorator(opts.AccountKeeper, opts.BankKeeper, opts.FeegrantKeeper), 45 | // SetPubKeyDecorator must be called before all signature verification decorators 46 | ante.NewSetPubKeyDecorator(opts.AccountKeeper), 47 | ante.NewValidateSigCountDecorator(opts.AccountKeeper), 48 | ante.NewSigGasConsumeDecorator(opts.AccountKeeper, sigGasConsumer), 49 | ante.NewSigVerificationDecorator(opts.AccountKeeper, opts.SignModeHandler), 50 | ante.NewIncrementSequenceDecorator(opts.AccountKeeper), 51 | ibcante.NewAnteDecorator(opts.IBCkeeper), 52 | } 53 | 54 | return sdk.ChainAnteDecorators(anteDecorators...), nil 55 | } 56 | -------------------------------------------------------------------------------- /ante/ante_test.go: -------------------------------------------------------------------------------- 1 | package ante_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/cosmos/cosmos-sdk/client" 8 | "github.com/cosmos/cosmos-sdk/client/tx" 9 | cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 10 | "github.com/cosmos/cosmos-sdk/simapp" 11 | "github.com/cosmos/cosmos-sdk/testutil/testdata" 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | "github.com/cosmos/cosmos-sdk/types/tx/signing" 14 | xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" 15 | "github.com/stretchr/testify/suite" 16 | tmrand "github.com/tendermint/tendermint/libs/rand" 17 | tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 18 | 19 | "github.com/notional-labs/fee-abstraction/v2/app" 20 | apphelpers "github.com/notional-labs/fee-abstraction/v2/app/helpers" 21 | ) 22 | 23 | type IntegrationTestSuite struct { 24 | suite.Suite 25 | 26 | app *app.FeeAbs 27 | // anteHandler sdk.AnteHandler 28 | ctx sdk.Context 29 | clientCtx client.Context 30 | txBuilder client.TxBuilder 31 | } 32 | 33 | func TestIntegrationTestSuite(t *testing.T) { 34 | suite.Run(t, new(IntegrationTestSuite)) 35 | } 36 | 37 | func (s *IntegrationTestSuite) SetupTest() { 38 | app := apphelpers.Setup(s.T(), false, 1) 39 | ctx := app.BaseApp.NewContext(false, tmproto.Header{ 40 | ChainID: fmt.Sprintf("test-chain-%s", tmrand.Str(4)), 41 | Height: 1, 42 | }) 43 | 44 | encodingConfig := simapp.MakeTestEncodingConfig() 45 | encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) 46 | testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry) 47 | 48 | s.app = app 49 | s.ctx = ctx 50 | s.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) 51 | } 52 | 53 | func (s *IntegrationTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) { 54 | var sigsV2 []signing.SignatureV2 55 | for i, priv := range privs { 56 | sigV2 := signing.SignatureV2{ 57 | PubKey: priv.PubKey(), 58 | Data: &signing.SingleSignatureData{ 59 | SignMode: s.clientCtx.TxConfig.SignModeHandler().DefaultMode(), 60 | Signature: nil, 61 | }, 62 | Sequence: accSeqs[i], 63 | } 64 | 65 | sigsV2 = append(sigsV2, sigV2) 66 | } 67 | 68 | if err := s.txBuilder.SetSignatures(sigsV2...); err != nil { 69 | return nil, err 70 | } 71 | 72 | sigsV2 = []signing.SignatureV2{} 73 | for i, priv := range privs { 74 | signerData := xauthsigning.SignerData{ 75 | ChainID: chainID, 76 | AccountNumber: accNums[i], 77 | Sequence: accSeqs[i], 78 | } 79 | sigV2, err := tx.SignWithPrivKey( 80 | s.clientCtx.TxConfig.SignModeHandler().DefaultMode(), 81 | signerData, 82 | s.txBuilder, 83 | priv, 84 | s.clientCtx.TxConfig, 85 | accSeqs[i], 86 | ) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | sigsV2 = append(sigsV2, sigV2) 92 | } 93 | 94 | if err := s.txBuilder.SetSignatures(sigsV2...); err != nil { 95 | return nil, err 96 | } 97 | 98 | return s.txBuilder.GetTx(), nil 99 | } 100 | -------------------------------------------------------------------------------- /ante/fee.go: -------------------------------------------------------------------------------- 1 | package ante 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | tmstrings "github.com/tendermint/tendermint/libs/strings" 7 | ) 8 | 9 | const maxBypassMinFeeMsgGasUsage = uint64(200_000) 10 | 11 | // MempoolFeeDecorator will check if the transaction's fee is at least as large 12 | // as the local validator's minimum gasFee (defined in validator config). 13 | // 14 | // If fee is too low, decorator returns error and tx is rejected from mempool. 15 | // Note this only applies when ctx.CheckTx = true. If fee is high enough or not 16 | // CheckTx, then call next AnteHandler. 17 | // 18 | // CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator 19 | type MempoolFeeDecorator struct { 20 | BypassMinFeeMsgTypes []string 21 | } 22 | 23 | func NewMempoolFeeDecorator(bypassMsgTypes []string) MempoolFeeDecorator { 24 | return MempoolFeeDecorator{ 25 | BypassMinFeeMsgTypes: bypassMsgTypes, 26 | } 27 | } 28 | 29 | func (mfd MempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { 30 | feeTx, ok := tx.(sdk.FeeTx) 31 | if !ok { 32 | return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") 33 | } 34 | 35 | feeCoins := feeTx.GetFee() 36 | gas := feeTx.GetGas() 37 | msgs := feeTx.GetMsgs() 38 | 39 | // Only check for minimum fees if the execution mode is CheckTx and the tx does 40 | // not contain operator configured bypass messages. If the tx does contain 41 | // operator configured bypass messages only, it's total gas must be less than 42 | // or equal to a constant, otherwise minimum fees are checked to prevent spam. 43 | if ctx.IsCheckTx() && !simulate && !(mfd.bypassMinFeeMsgs(msgs) && gas <= uint64(len(msgs))*maxBypassMinFeeMsgGasUsage) { 44 | minGasPrices := ctx.MinGasPrices() 45 | if !minGasPrices.IsZero() { 46 | requiredFees := make(sdk.Coins, len(minGasPrices)) 47 | 48 | // Determine the required fees by multiplying each required minimum gas 49 | // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). 50 | glDec := sdk.NewDec(int64(gas)) 51 | for i, gp := range minGasPrices { 52 | fee := gp.Amount.Mul(glDec) 53 | requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) 54 | } 55 | 56 | if !feeCoins.IsAnyGTE(requiredFees) { 57 | return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) 58 | } 59 | } 60 | } 61 | 62 | return next(ctx, tx, simulate) 63 | } 64 | 65 | func (mfd MempoolFeeDecorator) bypassMinFeeMsgs(msgs []sdk.Msg) bool { 66 | for _, msg := range msgs { 67 | if tmstrings.StringInSlice(sdk.MsgTypeURL(msg), mfd.BypassMinFeeMsgTypes) { 68 | continue 69 | } 70 | 71 | return false 72 | } 73 | 74 | return true 75 | } 76 | -------------------------------------------------------------------------------- /ante/fee_test.go: -------------------------------------------------------------------------------- 1 | package ante_test 2 | 3 | import ( 4 | cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 5 | "github.com/cosmos/cosmos-sdk/testutil/testdata" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ibcclienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" 8 | ibcchanneltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" 9 | 10 | "github.com/notional-labs/fee-abstraction/v2/ante" 11 | ) 12 | 13 | func (s *IntegrationTestSuite) TestMempoolFeeDecorator() { 14 | s.SetupTest() 15 | s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() 16 | 17 | mfd := ante.NewMempoolFeeDecorator([]string{ 18 | sdk.MsgTypeURL(&ibcchanneltypes.MsgRecvPacket{}), 19 | sdk.MsgTypeURL(&ibcchanneltypes.MsgAcknowledgement{}), 20 | sdk.MsgTypeURL(&ibcclienttypes.MsgUpdateClient{}), 21 | }) 22 | antehandler := sdk.ChainAnteDecorators(mfd) 23 | priv1, _, addr1 := testdata.KeyTestPubAddr() 24 | 25 | msg := testdata.NewTestMsg(addr1) 26 | feeAmount := testdata.NewTestFeeAmount() 27 | gasLimit := testdata.NewTestGasLimit() 28 | s.Require().NoError(s.txBuilder.SetMsgs(msg)) 29 | s.txBuilder.SetFeeAmount(feeAmount) 30 | s.txBuilder.SetGasLimit(gasLimit) 31 | 32 | privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} 33 | tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) 34 | s.Require().NoError(err) 35 | 36 | // Set high gas price so standard test fee fails 37 | feeAmt := sdk.NewDecCoinFromDec("uatom", sdk.NewDec(200).Quo(sdk.NewDec(100000))) 38 | minGasPrice := []sdk.DecCoin{feeAmt} 39 | s.ctx = s.ctx.WithMinGasPrices(minGasPrice).WithIsCheckTx(true) 40 | 41 | // antehandler errors with insufficient fees 42 | _, err = antehandler(s.ctx, tx, false) 43 | s.Require().Error(err, "expected error due to low fee") 44 | 45 | // ensure no fees for certain IBC msgs 46 | s.Require().NoError(s.txBuilder.SetMsgs( 47 | ibcchanneltypes.NewMsgRecvPacket(ibcchanneltypes.Packet{}, nil, ibcclienttypes.Height{}, ""), 48 | )) 49 | 50 | oracleTx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) 51 | s.Require().NoError(err) 52 | _, err = antehandler(s.ctx, oracleTx, false) 53 | s.Require().NoError(err, "expected min fee bypass for IBC messages") 54 | 55 | s.ctx = s.ctx.WithIsCheckTx(false) 56 | 57 | // antehandler should not error since we do not check min gas prices in DeliverTx 58 | _, err = antehandler(s.ctx, tx, false) 59 | s.Require().NoError(err, "unexpected error during DeliverTx") 60 | } 61 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # Test feeabs and osmosis Interchain Query for spot price 2 | 3 | ## Setup 4 | ``` 5 | # Deploy chains 6 | ./scripts/node_start/runnode_custom.sh c 7 | ./scripts/node_start/runnode_osmosis.sh 8 | # Run relayer 9 | ./scripts/run_relayer.sh 10 | # Create an osmosis pool 11 | ./scripts/create_pool.sh 12 | # Deploy contract and create relayer channel 13 | ./scripts/deploy_and_channel.sh 14 | ``` 15 | 16 | ## Test 17 | ``` 18 | feeappd tx feeabs queryomosis --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 19 | # Wait for about 10 sec 20 | feeappd q feeabs osmo-spot-price 21 | ``` 22 | 23 | The result looks like this 24 | ``` 25 | base_asset: osmo 26 | quote_asset: stake 27 | spot_price: "2.000000000000000000" 28 | ``` 29 | 30 | ## Gov proposal 31 | 32 | ``` 33 | feeappd tx gov submit-proposal param-change scripts/proposal.json --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 34 | 35 | feeappd tx gov vote 1 yes --from feeapp1 --keyring-backend test --chain-id feeappd-t1 --yes 36 | 37 | feeappd tx gov submit-proposal add-hostzone-config scripts/host_zone.json --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 38 | 39 | feeappd tx gov vote 2 yes --from feeapp1 --keyring-backend test --chain-id feeappd-t1 --yes 40 | ``` 41 | 42 | ``` 43 | feeappd tx gov submit-proposal param-change scripts/proposal_query.json --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 44 | 45 | feeappd tx gov vote 1 yes --from feeapp1 --keyring-backend test --chain-id feeappd-t1 --yes 46 | 47 | feeappd tx gov submit-proposal add-hostzone-config scripts/host_zone_query.json --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 48 | 49 | feeappd tx gov vote 2 yes --from feeapp1 --keyring-backend test --chain-id feeappd-t1 --yes 50 | ``` 51 | 52 | ``` 53 | {ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518 ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878 channel-0 osmo1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqvlx82r 1 true true channel-1} 54 | B834FA96EB41DB72C1DFA61DAE0000C76065ADAC 55 | 0ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518 56 | ``` -------------------------------------------------------------------------------- /app/ante.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | "github.com/cosmos/cosmos-sdk/x/auth/ante" 7 | ibcante "github.com/cosmos/ibc-go/v4/modules/core/ante" 8 | ibckeeper "github.com/cosmos/ibc-go/v4/modules/core/keeper" 9 | 10 | feeabsante "github.com/notional-labs/fee-abstraction/v2/x/feeabs/ante" 11 | feeabskeeper "github.com/notional-labs/fee-abstraction/v2/x/feeabs/keeper" 12 | ) 13 | 14 | // HandlerOptions extends the SDK's AnteHandler options by requiring the IBC 15 | type HandlerOptions struct { 16 | ante.HandlerOptions 17 | 18 | IBCKeeper *ibckeeper.Keeper 19 | FeeAbskeeper feeabskeeper.Keeper 20 | } 21 | 22 | // NewAnteHandler returns an AnteHandler that checks and increments sequence 23 | // numbers, checks signatures & account numbers, and deducts fees from the first 24 | // signer. 25 | func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { 26 | if options.AccountKeeper == nil { 27 | return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder") 28 | } 29 | 30 | if options.BankKeeper == nil { 31 | return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder") 32 | } 33 | 34 | if options.SignModeHandler == nil { 35 | return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") 36 | } 37 | 38 | sigGasConsumer := options.SigGasConsumer 39 | if sigGasConsumer == nil { 40 | sigGasConsumer = ante.DefaultSigVerificationGasConsumer 41 | } 42 | 43 | anteDecorators := []sdk.AnteDecorator{ 44 | ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first 45 | ante.NewRejectExtensionOptionsDecorator(), 46 | feeabsante.NewFeeAbstrationMempoolFeeDecorator(options.FeeAbskeeper), 47 | ante.NewValidateBasicDecorator(), 48 | ante.NewTxTimeoutHeightDecorator(), 49 | ante.NewValidateMemoDecorator(options.AccountKeeper), 50 | ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), 51 | feeabsante.NewFeeAbstractionDeductFeeDecorate(options.AccountKeeper, options.BankKeeper, options.FeeAbskeeper, options.FeegrantKeeper), 52 | // SetPubKeyDecorator must be called before all signature verification decorators 53 | ante.NewSetPubKeyDecorator(options.AccountKeeper), 54 | ante.NewValidateSigCountDecorator(options.AccountKeeper), 55 | ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), 56 | ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), 57 | ante.NewIncrementSequenceDecorator(options.AccountKeeper), 58 | ibcante.NewAnteDecorator(options.IBCKeeper), 59 | } 60 | 61 | return sdk.ChainAnteDecorators(anteDecorators...), nil 62 | } 63 | -------------------------------------------------------------------------------- /app/const.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // nolint: gosec 4 | const ( 5 | appName = "FeeAbs" 6 | upgradeName = "v7-Theta" 7 | 8 | authzMsgExec = "/cosmos.authz.v1beta1.MsgExec" 9 | authzMsgGrant = "/cosmos.authz.v1beta1.MsgGrant" 10 | authzMsgRevoke = "/cosmos.authz.v1beta1.MsgRevoke" 11 | bankMsgSend = "/cosmos.bank.v1beta1.MsgSend" 12 | bankMsgMultiSend = "/cosmos.bank.v1beta1.MsgMultiSend" 13 | distrMsgSetWithdrawAddr = "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress" 14 | distrMsgWithdrawValidatorCommission = "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission" 15 | distrMsgFundCommunityPool = "/cosmos.distribution.v1beta1.MsgFundCommunityPool" 16 | distrMsgWithdrawDelegatorReward = "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" 17 | feegrantMsgGrantAllowance = "/cosmos.feegrant.v1beta1.MsgGrantAllowance" 18 | feegrantMsgRevokeAllowance = "/cosmos.feegrant.v1beta1.MsgRevokeAllowance" 19 | govMsgVoteWeighted = "/cosmos.gov.v1beta1.MsgVoteWeighted" 20 | govMsgSubmitProposal = "/cosmos.gov.v1beta1.MsgSubmitProposal" 21 | govMsgDeposit = "/cosmos.gov.v1beta1.MsgDeposit" 22 | govMsgVote = "/cosmos.gov.v1beta1.MsgVote" 23 | stakingMsgEditValidator = "/cosmos.staking.v1beta1.MsgEditValidator" 24 | stakingMsgDelegate = "/cosmos.staking.v1beta1.MsgDelegate" 25 | stakingMsgUndelegate = "/cosmos.staking.v1beta1.MsgUndelegate" 26 | stakingMsgBeginRedelegate = "/cosmos.staking.v1beta1.MsgBeginRedelegate" 27 | stakingMsgCreateValidator = "/cosmos.staking.v1beta1.MsgCreateValidator" 28 | vestingMsgCreateVestingAccount = "/cosmos.vesting.v1beta1.MsgCreateVestingAccount" 29 | transferMsgTransfer = "/ibc.applications.transfer.v1.MsgTransfer" 30 | liquidityMsgCreatePool = "/tendermint.liquidity.v1beta1.MsgCreatePool" 31 | liquidityMsgSwapWithinBatch = "/tendermint.liquidity.v1beta1.MsgSwapWithinBatch" 32 | liquidityMsgDepositWithinBatch = "/tendermint.liquidity.v1beta1.MsgDepositWithinBatch" 33 | liquidityMsgWithdrawWithinBatch = "/tendermint.liquidity.v1beta1.MsgWithdrawWithinBatch" 34 | ) 35 | -------------------------------------------------------------------------------- /app/encoding.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/notional-labs/fee-abstraction/v2/app/params" 5 | 6 | "github.com/cosmos/cosmos-sdk/std" 7 | ) 8 | 9 | // MakeEncodingConfig creates an EncodingConfig for testing 10 | func MakeEncodingConfig() params.EncodingConfig { 11 | encodingConfig := params.MakeEncodingConfig() 12 | std.RegisterLegacyAminoCodec(encodingConfig.Amino) 13 | std.RegisterInterfaces(encodingConfig.InterfaceRegistry) 14 | ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) 15 | ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) 16 | return encodingConfig 17 | } 18 | -------------------------------------------------------------------------------- /app/genesis.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // The genesis state of the blockchain is represented here as a map of raw json 8 | // messages key'd by a identifier string. 9 | // The identifier is used to determine which module genesis information belongs 10 | // to so it may be appropriately routed during init chain. 11 | // Within this application default genesis information is retrieved from 12 | // the ModuleBasicManager which populates json from each BasicModule 13 | // object provided to it during init. 14 | type GenesisState map[string]json.RawMessage 15 | 16 | // NewDefaultGenesisState generates the default state for the application. 17 | func NewDefaultGenesisState() GenesisState { 18 | encCfg := MakeEncodingConfig() 19 | return ModuleBasics.DefaultGenesis(encCfg.Marshaler) 20 | } 21 | -------------------------------------------------------------------------------- /app/genesis_account.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "errors" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 8 | ) 9 | 10 | var _ authtypes.GenesisAccount = (*SimGenesisAccount)(nil) 11 | 12 | // SimGenesisAccount defines a type that implements the GenesisAccount interface 13 | // to be used for simulation accounts in the genesis state. 14 | type SimGenesisAccount struct { 15 | *authtypes.BaseAccount 16 | 17 | // vesting account fields 18 | OriginalVesting sdk.Coins `json:"original_vesting" yaml:"original_vesting"` // total vesting coins upon initialization 19 | DelegatedFree sdk.Coins `json:"delegated_free" yaml:"delegated_free"` // delegated vested coins at time of delegation 20 | DelegatedVesting sdk.Coins `json:"delegated_vesting" yaml:"delegated_vesting"` // delegated vesting coins at time of delegation 21 | StartTime int64 `json:"start_time" yaml:"start_time"` // vesting start time (UNIX Epoch time) 22 | EndTime int64 `json:"end_time" yaml:"end_time"` // vesting end time (UNIX Epoch time) 23 | 24 | // module account fields 25 | ModuleName string `json:"module_name" yaml:"module_name"` // name of the module account 26 | ModulePermissions []string `json:"module_permissions" yaml:"module_permissions"` // permissions of module account 27 | } 28 | 29 | // Validate checks for errors on the vesting and module account parameters 30 | func (sga SimGenesisAccount) Validate() error { 31 | if !sga.OriginalVesting.IsZero() { 32 | if sga.StartTime >= sga.EndTime { 33 | return errors.New("vesting start-time cannot be before end-time") 34 | } 35 | } 36 | 37 | if sga.ModuleName != "" { 38 | ma := authtypes.ModuleAccount{ 39 | BaseAccount: sga.BaseAccount, Name: sga.ModuleName, Permissions: sga.ModulePermissions, 40 | } 41 | if err := ma.Validate(); err != nil { 42 | return err 43 | } 44 | } 45 | 46 | return sga.BaseAccount.Validate() 47 | } 48 | -------------------------------------------------------------------------------- /app/helpers/test_helpers.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 10 | "github.com/stretchr/testify/require" 11 | abci "github.com/tendermint/tendermint/abci/types" 12 | "github.com/tendermint/tendermint/libs/log" 13 | tmrand "github.com/tendermint/tendermint/libs/rand" 14 | tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 15 | tmtypes "github.com/tendermint/tendermint/types" 16 | dbm "github.com/tendermint/tm-db" 17 | 18 | "github.com/CosmWasm/wasmd/x/wasm" 19 | sdk "github.com/cosmos/cosmos-sdk/types" 20 | feeapp "github.com/notional-labs/fee-abstraction/v2/app" 21 | ) 22 | 23 | // SimAppChainID hardcoded chainID for simulation 24 | const ( 25 | SimAppChainID = "fee-app" 26 | ) 27 | 28 | // DefaultConsensusParams defines the default Tendermint consensus params used 29 | // in feeapp testing. 30 | var DefaultConsensusParams = &abci.ConsensusParams{ 31 | Block: &abci.BlockParams{ 32 | MaxBytes: 200000, 33 | MaxGas: 2000000, 34 | }, 35 | Evidence: &tmproto.EvidenceParams{ 36 | MaxAgeNumBlocks: 302400, 37 | MaxAgeDuration: 504 * time.Hour, // 3 weeks is the max duration 38 | MaxBytes: 10000, 39 | }, 40 | Validator: &tmproto.ValidatorParams{ 41 | PubKeyTypes: []string{ 42 | tmtypes.ABCIPubKeyTypeEd25519, 43 | }, 44 | }, 45 | } 46 | 47 | type EmptyAppOptions struct{} 48 | 49 | func (EmptyAppOptions) Get(o string) interface{} { return nil } 50 | 51 | func NewContextForApp(app feeapp.FeeAbs) sdk.Context { 52 | ctx := app.BaseApp.NewContext(false, tmproto.Header{ 53 | ChainID: fmt.Sprintf("test-chain-%s", tmrand.Str(4)), 54 | Height: 1, 55 | }) 56 | return ctx 57 | } 58 | 59 | func Setup(t *testing.T, isCheckTx bool, invCheckPeriod uint) *feeapp.FeeAbs { 60 | t.Helper() 61 | 62 | app, genesisState := setup(!isCheckTx, invCheckPeriod) 63 | if !isCheckTx { 64 | // InitChain must be called to stop deliverState from being nil 65 | stateBytes, err := json.MarshalIndent(genesisState, "", " ") 66 | require.NoError(t, err) 67 | 68 | // Initialize the chain 69 | app.InitChain( 70 | abci.RequestInitChain{ 71 | Validators: []abci.ValidatorUpdate{}, 72 | ConsensusParams: DefaultConsensusParams, 73 | AppStateBytes: stateBytes, 74 | }, 75 | ) 76 | } 77 | 78 | return app 79 | } 80 | 81 | func setup(withGenesis bool, invCheckPeriod uint) (*feeapp.FeeAbs, feeapp.GenesisState) { 82 | db := dbm.NewMemDB() 83 | encCdc := feeapp.MakeEncodingConfig() 84 | var emptyWasmOpts []wasm.Option 85 | app := feeapp.NewFeeAbs( 86 | log.NewNopLogger(), 87 | db, 88 | nil, 89 | true, 90 | map[int64]bool{}, 91 | feeapp.DefaultNodeHome, 92 | invCheckPeriod, 93 | encCdc, 94 | EmptyAppOptions{}, 95 | emptyWasmOpts, 96 | ) 97 | if withGenesis { 98 | return app, feeapp.NewDefaultGenesisState() 99 | } 100 | 101 | return app, feeapp.GenesisState{} 102 | } 103 | 104 | func AddHostZoneProposalFixture(mutators ...func(*types.AddHostZoneProposal)) *types.AddHostZoneProposal { 105 | p := &types.AddHostZoneProposal{ 106 | Title: "Title", 107 | Description: "Description", 108 | HostChainConfig: &types.HostChainFeeAbsConfig{}, 109 | } 110 | for _, m := range mutators { 111 | m(p) 112 | } 113 | 114 | return p 115 | } 116 | -------------------------------------------------------------------------------- /app/params/config.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | serverconfig "github.com/cosmos/cosmos-sdk/server/config" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | "github.com/cosmos/cosmos-sdk/types/address" 7 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 8 | ) 9 | 10 | const ( 11 | // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address. 12 | Bech32PrefixAccAddr = "feeabs" 13 | ) 14 | 15 | var ( 16 | // CustomConfigTemplate defines fee's custom application configuration TOML 17 | // template. It extends the core SDK template. 18 | CustomConfigTemplate = serverconfig.DefaultConfigTemplate + `` 19 | // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key. 20 | Bech32PrefixAccPub = Bech32PrefixAccAddr + "pub" 21 | // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address. 22 | Bech32PrefixValAddr = Bech32PrefixAccAddr + "valoper" 23 | // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key. 24 | Bech32PrefixValPub = Bech32PrefixAccAddr + "valoperpub" 25 | // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address. 26 | Bech32PrefixConsAddr = Bech32PrefixAccAddr + "valcons" 27 | // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key. 28 | Bech32PrefixConsPub = Bech32PrefixAccAddr + "valconspub" 29 | ) 30 | 31 | func init() { 32 | SetAddressPrefixes() 33 | } 34 | 35 | // CustomAppConfig defines Gaia's custom application configuration. 36 | type CustomAppConfig struct { 37 | serverconfig.Config 38 | 39 | // BypassMinFeeMsgTypes defines custom message types the operator may set that 40 | // will bypass minimum fee checks during CheckTx. 41 | BypassMinFeeMsgTypes []string `mapstructure:"bypass-min-fee-msg-types"` 42 | } 43 | 44 | // SetAddressPrefixes builds the Config with Bech32 addressPrefix and publKeyPrefix for accounts, validators, and consensus nodes and verifies that addreeses have correct format. 45 | func SetAddressPrefixes() { 46 | config := sdk.GetConfig() 47 | config.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) 48 | config.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) 49 | config.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) 50 | 51 | // This is copied from the cosmos sdk v0.43.0-beta1 52 | // source: https://github.com/cosmos/cosmos-sdk/blob/v0.43.0-beta1/types/address.go#L141 53 | config.SetAddressVerifier(func(bytes []byte) error { 54 | if len(bytes) == 0 { 55 | return sdkerrors.Wrap(sdkerrors.ErrUnknownAddress, "addresses cannot be empty") 56 | } 57 | 58 | if len(bytes) > address.MaxAddrLen { 59 | return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", address.MaxAddrLen, len(bytes)) 60 | } 61 | 62 | // TODO: Do we want to allow addresses of lengths other than 20 and 32 bytes? 63 | if len(bytes) != 20 && len(bytes) != 32 { 64 | return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "address length must be 20 or 32 bytes, got %d", len(bytes)) 65 | } 66 | 67 | return nil 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /app/params/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package params defines the simulation parameters. 3 | 4 | It contains the default weights used for each transaction used on the module's 5 | simulation. These weights define the chance for a transaction to be simulated at 6 | any gived operation. 7 | 8 | You can repace the default values for the weights by providing a params.json 9 | file with the weights defined for each of the transaction operations: 10 | 11 | { 12 | "op_weight_msg_send": 60, 13 | "op_weight_msg_delegate": 100, 14 | } 15 | 16 | In the example above, the `MsgSend` has 60% chance to be simulated, while the 17 | `MsgDelegate` will always be simulated. 18 | */ 19 | package params 20 | -------------------------------------------------------------------------------- /app/params/encoding.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/client" 5 | "github.com/cosmos/cosmos-sdk/codec" 6 | "github.com/cosmos/cosmos-sdk/codec/types" 7 | ) 8 | 9 | // EncodingConfig specifies the concrete encoding types to use for a given app. 10 | // This is provided for compatibility between protobuf and amino implementations. 11 | type EncodingConfig struct { 12 | InterfaceRegistry types.InterfaceRegistry 13 | Marshaler codec.Codec 14 | TxConfig client.TxConfig 15 | Amino *codec.LegacyAmino 16 | } 17 | -------------------------------------------------------------------------------- /app/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Simulation parameter constants 4 | const ( 5 | StakePerAccount = "stake_per_account" 6 | InitiallyBondedValidators = "initially_bonded_validators" 7 | ) 8 | -------------------------------------------------------------------------------- /app/params/proto.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/types" 6 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 7 | ) 8 | 9 | // MakeEncodingConfig creates an EncodingConfig for an amino based test configuration. 10 | func MakeEncodingConfig() EncodingConfig { 11 | amino := codec.NewLegacyAmino() 12 | interfaceRegistry := types.NewInterfaceRegistry() 13 | marshaler := codec.NewProtoCodec(interfaceRegistry) 14 | txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes) 15 | 16 | return EncodingConfig{ 17 | InterfaceRegistry: interfaceRegistry, 18 | Marshaler: marshaler, 19 | TxConfig: txCfg, 20 | Amino: amino, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/params/weights.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Default simulation operation weights for messages and gov proposals 4 | const ( 5 | DefaultWeightMsgSend int = 100 6 | DefaultWeightMsgMultiSend int = 10 7 | DefaultWeightMsgSetWithdrawAddress int = 50 8 | DefaultWeightMsgWithdrawDelegationReward int = 50 9 | DefaultWeightMsgWithdrawValidatorCommission int = 50 10 | DefaultWeightMsgFundCommunityPool int = 50 11 | DefaultWeightMsgDeposit int = 100 12 | DefaultWeightMsgVote int = 67 13 | DefaultWeightMsgUnjail int = 100 14 | DefaultWeightMsgCreateValidator int = 100 15 | DefaultWeightMsgEditValidator int = 5 16 | DefaultWeightMsgDelegate int = 100 17 | DefaultWeightMsgUndelegate int = 100 18 | DefaultWeightMsgBeginRedelegate int = 100 19 | 20 | DefaultWeightCommunitySpendProposal int = 5 21 | DefaultWeightTextProposal int = 5 22 | DefaultWeightParamChangeProposal int = 5 23 | ) 24 | -------------------------------------------------------------------------------- /app/sim_test.go: -------------------------------------------------------------------------------- 1 | package app_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | feeapp "github.com/notional-labs/fee-abstraction/v2/app" 10 | 11 | "github.com/notional-labs/fee-abstraction/v2/app/helpers" 12 | "github.com/stretchr/testify/require" 13 | "github.com/tendermint/tendermint/libs/log" 14 | "github.com/tendermint/tendermint/libs/rand" 15 | dbm "github.com/tendermint/tm-db" 16 | 17 | "github.com/CosmWasm/wasmd/x/wasm" 18 | "github.com/cosmos/cosmos-sdk/baseapp" 19 | "github.com/cosmos/cosmos-sdk/simapp" 20 | "github.com/cosmos/cosmos-sdk/store" 21 | simulation2 "github.com/cosmos/cosmos-sdk/types/simulation" 22 | "github.com/cosmos/cosmos-sdk/x/simulation" 23 | ) 24 | 25 | func init() { 26 | simapp.GetSimulatorFlags() 27 | } 28 | 29 | // Profile with: 30 | // /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/feeappApp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out 31 | func BenchmarkFullAppSimulation(b *testing.B) { 32 | config, db, dir, logger, _, err := simapp.SetupSimulation("goleveldb-app-sim", "Simulation") 33 | if err != nil { 34 | b.Fatalf("simulation setup failed: %s", err.Error()) 35 | } 36 | 37 | defer func() { 38 | db.Close() 39 | err = os.RemoveAll(dir) 40 | if err != nil { 41 | b.Fatal(err) 42 | } 43 | }() 44 | 45 | var emptyWasmOpts []wasm.Option 46 | app := feeapp.NewFeeAbs(logger, db, nil, true, map[int64]bool{}, feeapp.DefaultNodeHome, simapp.FlagPeriodValue, feeapp.MakeEncodingConfig(), simapp.EmptyAppOptions{}, emptyWasmOpts, interBlockCacheOpt()) 47 | 48 | // Run randomized simulation:w 49 | _, simParams, simErr := simulation.SimulateFromSeed( 50 | b, 51 | os.Stdout, 52 | app.BaseApp, 53 | simapp.AppStateFn(app.AppCodec(), app.SimulationManager()), 54 | simulation2.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 55 | simapp.SimulationOperations(app, app.AppCodec(), config), 56 | app.ModuleAccountAddrs(), 57 | config, 58 | app.AppCodec(), 59 | ) 60 | 61 | // export state and simParams before the simulation error is checked 62 | if err = simapp.CheckExportSimulation(app, config, simParams); err != nil { 63 | b.Fatal(err) 64 | } 65 | 66 | if simErr != nil { 67 | b.Fatal(simErr) 68 | } 69 | 70 | if config.Commit { 71 | simapp.PrintStats(db) 72 | } 73 | } 74 | 75 | // interBlockCacheOpt returns a BaseApp option function that sets the persistent 76 | // inter-block write-through cache. 77 | func interBlockCacheOpt() func(*baseapp.BaseApp) { 78 | return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) 79 | } 80 | 81 | func TestAppStateDeterminism(t *testing.T) { 82 | if !simapp.FlagEnabledValue { 83 | t.Skip("skipping application simulation") 84 | } 85 | 86 | config := simapp.NewConfigFromFlags() 87 | config.InitialBlockHeight = 1 88 | config.ExportParamsPath = "" 89 | config.OnOperation = false 90 | config.AllInvariants = false 91 | config.ChainID = helpers.SimAppChainID 92 | 93 | numSeeds := 3 94 | numTimesToRunPerSeed := 5 95 | appHashList := make([]json.RawMessage, numTimesToRunPerSeed) 96 | 97 | for i := 0; i < numSeeds; i++ { 98 | config.Seed = rand.Int63() 99 | 100 | for j := 0; j < numTimesToRunPerSeed; j++ { 101 | var logger log.Logger 102 | if simapp.FlagVerboseValue { 103 | logger = log.TestingLogger() 104 | } else { 105 | logger = log.NewNopLogger() 106 | } 107 | 108 | db := dbm.NewMemDB() 109 | var emptyWasmOpts []wasm.Option 110 | app := feeapp.NewFeeAbs(logger, db, nil, true, map[int64]bool{}, feeapp.DefaultNodeHome, simapp.FlagPeriodValue, feeapp.MakeEncodingConfig(), simapp.EmptyAppOptions{}, emptyWasmOpts, interBlockCacheOpt()) 111 | 112 | fmt.Printf( 113 | "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", 114 | config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, 115 | ) 116 | 117 | _, _, err := simulation.SimulateFromSeed( 118 | t, 119 | os.Stdout, 120 | app.BaseApp, 121 | simapp.AppStateFn(app.AppCodec(), app.SimulationManager()), 122 | simulation2.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 123 | simapp.SimulationOperations(app, app.AppCodec(), config), 124 | app.ModuleAccountAddrs(), 125 | config, 126 | app.AppCodec(), 127 | ) 128 | require.NoError(t, err) 129 | 130 | if config.Commit { 131 | simapp.PrintStats(db) 132 | } 133 | 134 | appHash := app.LastCommitID().Hash 135 | appHashList[j] = appHash 136 | 137 | if j != 0 { 138 | require.Equal( 139 | t, string(appHashList[0]), string(appHashList[j]), 140 | "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, 141 | ) 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/test_access.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cosmos/cosmos-sdk/baseapp" 7 | "github.com/cosmos/cosmos-sdk/client" 8 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/keeper" 9 | 10 | "github.com/CosmWasm/wasmd/app/params" 11 | 12 | "github.com/cosmos/cosmos-sdk/codec" 13 | bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" 14 | capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" 15 | stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" 16 | ibctransferkeeper "github.com/cosmos/ibc-go/v4/modules/apps/transfer/keeper" 17 | ibckeeper "github.com/cosmos/ibc-go/v4/modules/core/keeper" 18 | 19 | wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" 20 | ) 21 | 22 | type TestSupport struct { 23 | t testing.TB 24 | app *FeeAbs 25 | } 26 | 27 | func NewTestSupport(t testing.TB, app *FeeAbs) *TestSupport { 28 | return &TestSupport{t: t, app: app} 29 | } 30 | 31 | func (s TestSupport) IBCKeeper() *ibckeeper.Keeper { 32 | return s.app.IBCKeeper 33 | } 34 | 35 | func (s TestSupport) WasmKeeper() wasmkeeper.Keeper { 36 | return s.app.WasmKeeper 37 | } 38 | 39 | func (s TestSupport) AppCodec() codec.Codec { 40 | return s.app.appCodec 41 | } 42 | 43 | func (s TestSupport) ScopedWasmIBCKeeper() capabilitykeeper.ScopedKeeper { 44 | return s.app.ScopedWasmKeeper 45 | } 46 | 47 | func (s TestSupport) ScopeIBCKeeper() capabilitykeeper.ScopedKeeper { 48 | return s.app.ScopedIBCKeeper 49 | } 50 | 51 | func (s TestSupport) ScopedTransferKeeper() capabilitykeeper.ScopedKeeper { 52 | return s.app.ScopedTransferKeeper 53 | } 54 | 55 | func (s TestSupport) StakingKeeper() stakingkeeper.Keeper { 56 | return s.app.StakingKeeper 57 | } 58 | 59 | func (s TestSupport) BankKeeper() bankkeeper.Keeper { 60 | return s.app.BankKeeper 61 | } 62 | 63 | func (s TestSupport) TransferKeeper() ibctransferkeeper.Keeper { 64 | return s.app.TransferKeeper 65 | } 66 | 67 | func (s TestSupport) FeeAbsKeeper() keeper.Keeper { 68 | return s.app.FeeabsKeeper 69 | } 70 | 71 | func (s TestSupport) GetBaseApp() *baseapp.BaseApp { 72 | return s.app.BaseApp 73 | } 74 | 75 | func (s TestSupport) GetTxConfig() client.TxConfig { 76 | return params.MakeEncodingConfig().TxConfig 77 | } 78 | -------------------------------------------------------------------------------- /cmd/feeappd/cmd/root_test.go: -------------------------------------------------------------------------------- 1 | package cmd_test 2 | 3 | import ( 4 | "testing" 5 | 6 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 7 | "github.com/stretchr/testify/require" 8 | 9 | app "github.com/notional-labs/fee-abstraction/v2/app" 10 | "github.com/notional-labs/fee-abstraction/v2/cmd/feeappd/cmd" 11 | ) 12 | 13 | func TestRootCmdConfig(t *testing.T) { 14 | rootCmd, _ := cmd.NewRootCmd() 15 | rootCmd.SetArgs([]string{ 16 | "config", // Test the config cmd 17 | "keyring-backend", // key 18 | "test", // value 19 | }) 20 | 21 | require.NoError(t, svrcmd.Execute(rootCmd, app.DefaultNodeHome)) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/feeappd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cosmos/cosmos-sdk/server" 7 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 8 | 9 | app "github.com/notional-labs/fee-abstraction/v2/app" 10 | "github.com/notional-labs/fee-abstraction/v2/app/params" 11 | "github.com/notional-labs/fee-abstraction/v2/cmd/feeappd/cmd" 12 | ) 13 | 14 | func main() { 15 | params.SetAddressPrefixes() 16 | rootCmd, _ := cmd.NewRootCmd() 17 | 18 | if err := svrcmd.Execute(rootCmd, app.DefaultNodeHome); err != nil { 19 | switch e := err.(type) { 20 | case server.ErrorCode: 21 | os.Exit(e.Code) 22 | 23 | default: 24 | os.Exit(1) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /go.work.example: -------------------------------------------------------------------------------- 1 | go 1.18 2 | 3 | use ( 4 | ./ 5 | ./tests/interchaintest 6 | 7 | ) 8 | 9 | replace github.com/tendermint/tendermint => github.com/informalsystems/tendermint v0.34.24 10 | -------------------------------------------------------------------------------- /proto/Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile is used for proto generation 2 | # To build, run `make proto-image-build` 3 | 4 | FROM bufbuild/buf:1.7.0 as BUILDER 5 | 6 | FROM golang:1.19-alpine 7 | 8 | 9 | RUN apk add --no-cache \ 10 | nodejs \ 11 | npm \ 12 | git \ 13 | make 14 | 15 | ENV GOLANG_PROTOBUF_VERSION=1.28.0 \ 16 | GOGO_PROTOBUF_VERSION=1.3.2 \ 17 | GRPC_GATEWAY_VERSION=1.16.0 18 | 19 | 20 | RUN go install github.com/cosmos/cosmos-proto/cmd/protoc-gen-go-pulsar@latest 21 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v${GOLANG_PROTOBUF_VERSION} 22 | RUN go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION} \ 23 | github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@v${GRPC_GATEWAY_VERSION} 24 | 25 | # install all gogo protobuf binaries 26 | RUN git clone https://github.com/regen-network/protobuf.git; \ 27 | cd protobuf; \ 28 | go mod download; \ 29 | make install 30 | 31 | # we need to use git clone because we use 'replace' directive in go.mod 32 | # protoc-gen-gocosmos was moved to to in cosmos/gogoproto but pending a migration there. 33 | RUN git clone https://github.com/regen-network/cosmos-proto.git; \ 34 | cd cosmos-proto/protoc-gen-gocosmos; \ 35 | go install . 36 | 37 | RUN npm install -g swagger-combine 38 | 39 | COPY --from=BUILDER /usr/local/bin /usr/local/bin 40 | -------------------------------------------------------------------------------- /proto/buf.gen.gogo.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: gocosmos 4 | out: .. 5 | opt: plugins=grpc,Mgoogle/protobuf/duration.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/struct.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/wrappers.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types,Mcosmos/orm/v1alpha1/orm.proto=github.com/cosmos/cosmos-sdk/api/cosmos/orm/v1alpha1 6 | - name: grpc-gateway 7 | out: .. 8 | opt: logtostderr=true 9 | -------------------------------------------------------------------------------- /proto/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: cosmos 6 | repository: cosmos-proto 7 | commit: 1935555c206d4afb9e94615dfd0fad31 8 | - remote: buf.build 9 | owner: cosmos 10 | repository: cosmos-sdk 11 | commit: fe5ddc8e345e4caf919a9a120fc1b017 12 | - remote: buf.build 13 | owner: cosmos 14 | repository: gogo-proto 15 | commit: 34d970b699f84aa382f3c29773a60836 16 | - remote: buf.build 17 | owner: googleapis 18 | repository: googleapis 19 | commit: 75b4300737fb4efca0831636be94e517 20 | -------------------------------------------------------------------------------- /proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | name: buf.build/notional-labs/fee-abstraction 3 | deps: 4 | - buf.build/cosmos/cosmos-sdk 5 | - buf.build/cosmos/cosmos-proto 6 | - buf.build/cosmos/gogo-proto 7 | - buf.build/googleapis/googleapis 8 | breaking: 9 | use: 10 | - FILE 11 | lint: 12 | use: 13 | - DEFAULT 14 | - COMMENTS 15 | - FILE_LOWER_SNAKE_CASE 16 | except: 17 | - UNARY_RPC 18 | - COMMENT_FIELD 19 | - SERVICE_SUFFIX 20 | - PACKAGE_VERSION_SUFFIX 21 | - RPC_REQUEST_STANDARD_NAME 22 | ignore: 23 | - tendermint -------------------------------------------------------------------------------- /proto/feeabstraction/absfee/v1beta1/epoch.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package feeabstraction.absfee.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/protobuf/duration.proto"; 6 | import "google/protobuf/timestamp.proto"; 7 | 8 | option go_package = "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types"; 9 | 10 | message EpochInfo { 11 | // identifier is a unique reference to this particular timer. 12 | string identifier = 1; 13 | // start_time is the time at which the timer first ever ticks. 14 | // If start_time is in the future, the epoch will not begin until the start 15 | // time. 16 | google.protobuf.Timestamp start_time = 2 [ 17 | (gogoproto.stdtime) = true, 18 | (gogoproto.nullable) = false, 19 | (gogoproto.moretags) = "yaml:\"start_time\"" 20 | ]; 21 | // duration is the time in between epoch ticks. 22 | // In order for intended behavior to be met, duration should 23 | // be greater than the chains expected block time. 24 | // Duration must be non-zero. 25 | google.protobuf.Duration duration = 3 [ 26 | (gogoproto.nullable) = false, 27 | (gogoproto.stdduration) = true, 28 | (gogoproto.jsontag) = "duration,omitempty", 29 | (gogoproto.moretags) = "yaml:\"duration\"" 30 | ]; 31 | // current_epoch is the current epoch number, or in other words, 32 | // how many times has the timer 'ticked'. 33 | // The first tick (current_epoch=1) is defined as 34 | // the first block whose blocktime is greater than the EpochInfo start_time. 35 | int64 current_epoch = 4; 36 | // current_epoch_start_time describes the start time of the current timer 37 | // interval. The interval is (current_epoch_start_time, 38 | // current_epoch_start_time + duration] When the timer ticks, this is set to 39 | // current_epoch_start_time = last_epoch_start_time + duration only one timer 40 | // tick for a given identifier can occur per block. 41 | // 42 | // NOTE! The current_epoch_start_time may diverge significantly from the 43 | // wall-clock time the epoch began at. Wall-clock time of epoch start may be 44 | // >> current_epoch_start_time. Suppose current_epoch_start_time = 10, 45 | // duration = 5. Suppose the chain goes offline at t=14, and comes back online 46 | // at t=30, and produces blocks at every successive time. (t=31, 32, etc.) 47 | // * The t=30 block will start the epoch for (10, 15] 48 | // * The t=31 block will start the epoch for (15, 20] 49 | // * The t=32 block will start the epoch for (20, 25] 50 | // * The t=33 block will start the epoch for (25, 30] 51 | // * The t=34 block will start the epoch for (30, 35] 52 | // * The **t=36** block will start the epoch for (35, 40] 53 | google.protobuf.Timestamp current_epoch_start_time = 5 [ 54 | (gogoproto.stdtime) = true, 55 | (gogoproto.nullable) = false, 56 | (gogoproto.moretags) = "yaml:\"current_epoch_start_time\"" 57 | ]; 58 | // epoch_counting_started is a boolean, that indicates whether this 59 | // epoch timer has began yet. 60 | bool epoch_counting_started = 6; 61 | reserved 7; 62 | // current_epoch_start_height is the block height at which the current epoch 63 | // started. (The block height at which the timer last ticked) 64 | int64 current_epoch_start_height = 8; 65 | } -------------------------------------------------------------------------------- /proto/feeabstraction/absfee/v1beta1/feepool.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package feeabstraction.absfee.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/api/annotations.proto"; 6 | import "cosmos/base/query/v1beta1/pagination.proto"; 7 | import "feeabstraction/absfee/v1beta1/params.proto"; 8 | 9 | option go_package = "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types"; 10 | -------------------------------------------------------------------------------- /proto/feeabstraction/absfee/v1beta1/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package feeabstraction.absfee.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/api/annotations.proto"; 6 | import "cosmos/base/query/v1beta1/pagination.proto"; 7 | import "feeabstraction/absfee/v1beta1/params.proto"; 8 | import "feeabstraction/absfee/v1beta1/epoch.proto"; 9 | 10 | option go_package = "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types"; 11 | 12 | // Params defines the parameters for the feeabs module. 13 | message GenesisState { 14 | Params params = 1 [ 15 | (gogoproto.moretags) = "yaml:\"params\"", 16 | (gogoproto.nullable) = false 17 | ]; 18 | repeated EpochInfo epochs = 2 [ (gogoproto.nullable) = false ]; 19 | string port_id = 3; 20 | } 21 | -------------------------------------------------------------------------------- /proto/feeabstraction/absfee/v1beta1/osmosisibc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package feeabstraction.absfee.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/protobuf/timestamp.proto"; 6 | import "google/api/annotations.proto"; 7 | import "tendermint/abci/types.proto"; 8 | 9 | option go_package = "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types"; 10 | 11 | message QueryArithmeticTwapToNowRequest { 12 | uint64 pool_id = 1; 13 | string base_asset = 2; 14 | string quote_asset = 3; 15 | google.protobuf.Timestamp start_time = 4 [ 16 | (gogoproto.nullable) = false, 17 | (gogoproto.stdtime) = true, 18 | (gogoproto.moretags) = "yaml:\"start_time\"" 19 | ]; 20 | } 21 | 22 | message QueryArithmeticTwapToNowResponse { 23 | string arithmetic_twap = 1 [ 24 | (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", 25 | (gogoproto.moretags) = "yaml:\"arithmetic_twap\"", 26 | (gogoproto.nullable) = false 27 | ]; 28 | } 29 | 30 | message InterchainQueryRequest { 31 | bytes data = 1; 32 | string path = 2; 33 | } 34 | 35 | // InterchainQueryPacketData is comprised of raw query. 36 | message InterchainQueryPacketData { 37 | bytes data = 1; 38 | // optional memo 39 | string memo = 2; 40 | } 41 | 42 | // InterchainQueryPacketAck is comprised of an ABCI query response with 43 | // non-deterministic fields left empty (e.g. Codespace, Log, Info and ...). 44 | message InterchainQueryPacketAck { bytes data = 1; } 45 | 46 | message InterchainQueryRequestPacket { 47 | repeated InterchainQueryRequest requests = 1 [ (gogoproto.nullable) = false ]; 48 | } 49 | 50 | // CosmosQuery contains a list of tendermint ABCI query requests. It should be 51 | // used when sending queries to an SDK host chain. 52 | message CosmosQuery { 53 | repeated tendermint.abci.RequestQuery requests = 1 54 | [ (gogoproto.nullable) = false ]; 55 | } 56 | 57 | // CosmosResponse contains a list of tendermint ABCI query responses. It should 58 | // be used when receiving responses from an SDK host chain. 59 | message CosmosResponse { 60 | repeated tendermint.abci.ResponseQuery responses = 1 61 | [ (gogoproto.nullable) = false ]; 62 | } 63 | -------------------------------------------------------------------------------- /proto/feeabstraction/absfee/v1beta1/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package feeabstraction.absfee.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/api/annotations.proto"; 6 | import "cosmos/base/query/v1beta1/pagination.proto"; 7 | import "google/protobuf/duration.proto"; 8 | 9 | option go_package = "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types"; 10 | 11 | // Params defines the parameters for the feeabs module. 12 | message Params { 13 | // native ibced in osmosis 14 | string native_ibced_in_osmosis = 1; 15 | 16 | // osmosis query TWAP path 17 | string osmosis_query_twap_path = 2; 18 | 19 | // chain name for ibc path unwinding 20 | string chain_name = 3; 21 | 22 | // transfer channel for cross chain swap with osmosis 23 | string ibc_transfer_channel = 4; 24 | 25 | // query twap price icq channel with osmosis 26 | string ibc_query_icq_channel = 5; 27 | 28 | // osmosis crosschain swap contract address 29 | string osmosis_crosschain_swap_address = 6; 30 | } 31 | -------------------------------------------------------------------------------- /proto/feeabstraction/absfee/v1beta1/proposal.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package feeabstraction.absfee.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/protobuf/timestamp.proto"; 6 | import "google/api/annotations.proto"; 7 | 8 | option go_package = "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types"; 9 | 10 | message HostChainFeeAbsConfig { 11 | // ibc token is allowed to be used as fee token 12 | string ibc_denom = 1 [ (gogoproto.moretags) = "yaml:\"allowed_token\"" ]; 13 | // token_in in cross_chain swap contract. 14 | string osmosis_pool_token_denom_in = 2; 15 | // pool id 16 | uint64 pool_id = 3; 17 | // Frozen 18 | bool frozen = 4; 19 | } 20 | 21 | message AddHostZoneProposal { 22 | option (gogoproto.goproto_getters) = false; 23 | // the title of the proposal 24 | string title = 1; 25 | // the description of the proposal 26 | string description = 2; 27 | // the host chain config 28 | HostChainFeeAbsConfig host_chain_config = 3; 29 | } 30 | 31 | message DeleteHostZoneProposal { 32 | option (gogoproto.goproto_getters) = false; 33 | // the title of the proposal 34 | string title = 1; 35 | // the description of the proposal 36 | string description = 2; 37 | // the ibc denom of this token 38 | string ibc_denom = 3; 39 | } 40 | 41 | message SetHostZoneProposal { 42 | option (gogoproto.goproto_getters) = false; 43 | // the title of the proposal 44 | string title = 1; 45 | // the description of the proposal 46 | string description = 2; 47 | // the host chain config 48 | HostChainFeeAbsConfig host_chain_config = 3; 49 | } -------------------------------------------------------------------------------- /proto/feeabstraction/absfee/v1beta1/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package feeabstraction.absfee.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/api/annotations.proto"; 6 | import "feeabstraction/absfee/v1beta1/params.proto"; 7 | import "feeabstraction/absfee/v1beta1/osmosisibc.proto"; 8 | import "feeabstraction/absfee/v1beta1/proposal.proto"; 9 | import "cosmos/base/v1beta1/coin.proto"; 10 | 11 | option go_package = "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types"; 12 | 13 | // Query defines the gRPC querier service. 14 | service Query { 15 | // OsmosisSpotPrice return spot price of pair Osmo/nativeToken 16 | rpc OsmosisArithmeticTwap(QueryOsmosisArithmeticTwapRequest) 17 | returns (QueryOsmosisArithmeticTwapResponse) { 18 | option (google.api.http).get = 19 | "/fee-abstraction/feeabs/v1/osmosis-arithmetic-twap/{ibc_denom}"; 20 | } 21 | // FeeabsModuleBalances return total balances of feeabs module 22 | rpc FeeabsModuleBalances(QueryFeeabsModuleBalacesRequest) 23 | returns (QueryFeeabsModuleBalacesResponse) { 24 | option (google.api.http).get = "/fee-abstraction/feeabs/v1/module-balances"; 25 | } 26 | 27 | rpc HostChainConfig(QueryHostChainConfigRequest) 28 | returns (QueryHostChainConfigRespone) { 29 | option (google.api.http).get = 30 | "/fee-abstraction/feeabs/v1/host-chain-config/{ibc_denom}"; 31 | } 32 | 33 | rpc AllHostChainConfig(AllQueryHostChainConfigRequest) 34 | returns (AllQueryHostChainConfigRespone) { 35 | option (google.api.http).get = 36 | "/fee-abstraction/feeabs/v1/all-host-chain-config"; 37 | } 38 | } 39 | 40 | message QueryHostChainConfigRequest { string ibc_denom = 1; } 41 | 42 | message QueryHostChainConfigRespone { 43 | HostChainFeeAbsConfig host_chain_config = 1 [ 44 | (gogoproto.moretags) = "yaml:\"host_chain_config\"", 45 | (gogoproto.nullable) = false 46 | ]; 47 | } 48 | 49 | // QueryOsmosisSpotPriceRequest is the request type for the Query/Feeabs RPC 50 | // method. 51 | message QueryOsmosisArithmeticTwapRequest { string ibc_denom = 1; } 52 | 53 | message QueryOsmosisArithmeticTwapResponse { 54 | string arithmetic_twap = 1 [ 55 | (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", 56 | (gogoproto.moretags) = "yaml:\"arithmetic_twap\"", 57 | (gogoproto.nullable) = false 58 | ]; 59 | } 60 | 61 | // QueryFeeabsModuleBalacesRequest is the request type for the Query/Feeabs RPC 62 | // method. 63 | message QueryFeeabsModuleBalacesRequest {} 64 | 65 | message QueryFeeabsModuleBalacesResponse { 66 | repeated cosmos.base.v1beta1.Coin balances = 1 [ 67 | (gogoproto.nullable) = false, 68 | (gogoproto.moretags) = "yaml:\"balances\"", 69 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" 70 | ]; 71 | string address = 2; 72 | } 73 | 74 | message AllQueryHostChainConfigRequest {} 75 | 76 | message AllQueryHostChainConfigRespone { 77 | repeated HostChainFeeAbsConfig all_host_chain_config = 1 [ 78 | (gogoproto.moretags) = "yaml:\"all_host_chain_config\"", 79 | (gogoproto.nullable) = false 80 | ]; 81 | } -------------------------------------------------------------------------------- /proto/feeabstraction/absfee/v1beta1/tx.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package feeabstraction.absfee.v1beta1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/api/annotations.proto"; 6 | import "cosmos/base/query/v1beta1/pagination.proto"; 7 | import "feeabstraction/absfee/v1beta1/params.proto"; 8 | import "feeabstraction/absfee/v1beta1/epoch.proto"; 9 | import "cosmos/base/v1beta1/coin.proto"; 10 | import "google/protobuf/timestamp.proto"; 11 | 12 | option go_package = "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types"; 13 | 14 | // Msg is the cosmos.group.v1 Msg service. 15 | service Msg { 16 | rpc SendQueryIbcDenomTWAP(MsgSendQueryIbcDenomTWAP) 17 | returns (MsgSendQueryIbcDenomTWAPResponse); 18 | rpc SwapCrossChain(MsgSwapCrossChain) returns (MsgSwapCrossChainResponse); 19 | rpc FundFeeAbsModuleAccount(MsgFundFeeAbsModuleAccount) 20 | returns (MsgFundFeeAbsModuleAccountResponse); 21 | } 22 | 23 | // Msg fund module account 24 | message MsgFundFeeAbsModuleAccount { 25 | string from_address = 1; 26 | repeated cosmos.base.v1beta1.Coin amount = 2 [ 27 | (gogoproto.nullable) = false, 28 | (gogoproto.moretags) = "yaml:\"amount\"", 29 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" 30 | ]; 31 | } 32 | 33 | message MsgFundFeeAbsModuleAccountResponse {} 34 | 35 | // Params defines the parameters for the feeabs module. 36 | message MsgSendQueryIbcDenomTWAP { string from_address = 1; } 37 | 38 | message MsgSendQueryIbcDenomTWAPResponse {} 39 | 40 | // Params defines the parameters for the feeabs module. 41 | message MsgSwapCrossChain { 42 | string from_address = 1; 43 | string ibc_denom = 2; 44 | } 45 | message MsgSwapCrossChainResponse {} 46 | -------------------------------------------------------------------------------- /sample_pool.json: -------------------------------------------------------------------------------- 1 | { 2 | "weights": "1ibc/9117A26BA81E29FA4F78F57DC2BD90CD3D26848101BA880445F119B22A1E254E,1ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878", 3 | "initial-deposit": "500000000000ibc/9117A26BA81E29FA4F78F57DC2BD90CD3D26848101BA880445F119B22A1E254E,100000000000ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878", 4 | "swap-fee": "0.01", 5 | "exit-fee": "0", 6 | "future-governor": "168h" 7 | } 8 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Test feeabs and osmosis Interchain Query for spot price 2 | 3 | ## Setup 4 | ``` 5 | # Deploy chains 6 | ./scripts/node_start/runnode_custom.sh c 7 | ./scripts/node_start/runnode_osmosis.sh 8 | # Run relayer 9 | ./scripts/run_relayer.sh 10 | # Create an osmosis pool 11 | ./scripts/create_pool.sh 12 | # Deploy contract and create relayer channel 13 | ./scripts/deploy_and_channel.sh 14 | ``` 15 | 16 | ## Test 17 | ``` 18 | feeappd tx feeabs queryomosis --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 19 | # Wait for about 10 sec 20 | feeappd q feeabs osmo-spot-price 21 | ``` 22 | 23 | The result looks like this 24 | ``` 25 | base_asset: osmo 26 | quote_asset: stake 27 | spot_price: "2.000000000000000000" 28 | ``` 29 | 30 | ## Gov proposal 31 | 32 | ``` 33 | feeappd tx gov submit-proposal param-change scripts/proposal.json --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 34 | 35 | feeappd tx gov vote 1 yes --from feeapp1 --keyring-backend test --chain-id feeappd-t1 --yes 36 | 37 | feeappd tx gov submit-proposal add-hostzone-config scripts/host_zone_gaia.json --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 38 | 39 | feeappd tx gov vote 2 yes --from feeapp1 --keyring-backend test --chain-id feeappd-t1 --yes 40 | ``` 41 | 42 | ``` 43 | feeappd tx gov submit-proposal param-change scripts/proposal_query.json --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 44 | 45 | feeappd tx gov vote 1 yes --from feeapp1 --keyring-backend test --chain-id feeappd-t1 --yes 46 | 47 | feeappd tx gov submit-proposal add-hostzone-config scripts/host_zone_query.json --from feeacc --keyring-backend test --chain-id feeappd-t1 --yes 48 | 49 | feeappd tx gov vote 2 yes --from feeapp1 --keyring-backend test --chain-id feeappd-t1 --yes 50 | ``` 51 | 52 | ``` 53 | {ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518 ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878 channel-0 osmo1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqvlx82r 1 true true channel-1} 54 | B834FA96EB41DB72C1DFA61DAE0000C76065ADAC 55 | 0ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518 56 | ``` -------------------------------------------------------------------------------- /scripts/bytecode/crosschain_registry.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/scripts/bytecode/crosschain_registry.wasm -------------------------------------------------------------------------------- /scripts/bytecode/crosschain_swaps.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/scripts/bytecode/crosschain_swaps.wasm -------------------------------------------------------------------------------- /scripts/bytecode/fee_abstraction.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/scripts/bytecode/fee_abstraction.wasm -------------------------------------------------------------------------------- /scripts/bytecode/ibc_stargate-aarch64.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/scripts/bytecode/ibc_stargate-aarch64.wasm -------------------------------------------------------------------------------- /scripts/bytecode/ibc_stargate.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/scripts/bytecode/ibc_stargate.wasm -------------------------------------------------------------------------------- /scripts/bytecode/ibc_stargate2.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/scripts/bytecode/ibc_stargate2.wasm -------------------------------------------------------------------------------- /scripts/bytecode/swaprouter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/scripts/bytecode/swaprouter.wasm -------------------------------------------------------------------------------- /scripts/check-generated.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | make proto-gen 6 | -------------------------------------------------------------------------------- /scripts/deploy_and_channel.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | osmosisd tx wasm store scripts/bytecode/ibc_stargate.wasm --keyring-backend=test --from deployer --chain-id testing --gas 10000000 --fees 25000stake --yes 4 | 5 | sleep 5 6 | 7 | ID=3 8 | 9 | INIT='{"packet_lifetime":1000000}' 10 | osmosisd tx wasm instantiate 3 "$INIT" --keyring-backend=test --from deployer --chain-id testing --label "test" --no-admin --yes 11 | 12 | sleep 5 13 | CONTRACT=$(osmosisd query wasm list-contract-by-code $ID --output json | jq -r '.contracts[-1]') 14 | 15 | query_params='{"query_stargate_twap":{"pool_id":1,"token_in_denom":"uosmo","token_out_denom":"uatom","with_swap_fee":false}}' 16 | osmosisd query wasm contract-state smart osmo17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgs5yczr8 "$query_params" 17 | 18 | echo "feeabs contract: " 19 | echo $CONTRACT 20 | 21 | sleep 2 22 | 23 | if [[ $CONTRACT == null ]] 24 | then 25 | echo $CONTRACT 26 | echo "Contract deploy unsuccesful" 27 | else 28 | echo wasm.$CONTRACT 29 | hermes --config scripts/relayer_hermes/config.toml create channel --a-chain testing --b-chain feeappd-t1 --a-port wasm.osmo17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgs5yczr8 --b-port feeabs --new-client-connection --yes 30 | fi -------------------------------------------------------------------------------- /scripts/deploy_local.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | osmosisd tx wasm store scripts/bytecode/ibc_stargate-aarch64.wasm --keyring-backend=test --from validator1 --chain-id testing --gas 10000000 --fees 25000stake --yes 4 | 5 | sleep 2 6 | 7 | ID=3 8 | 9 | INIT='{"packet_lifetime":1000000000}' 10 | osmosisd tx wasm instantiate $ID "$INIT" --keyring-backend=test --from validator1 --chain-id testing --label "test" --no-admin --yes 11 | 12 | CONTRACT=$(osmosisd query wasm list-contract-by-code 3 --output json | jq -r '.contracts[-1]') 13 | echo $CONTRACT 14 | query_params='{"query_stargate_twap":{"pool_id":1,"token_in_denom":"uosmo","token_out_denom":"uatom","with_swap_fee":false}}' 15 | osmosisd query wasm contract-state smart osmo1eyfccmjm6732k7wp4p6gdjwhxjwsvje44j0hfx8nkgrm8fs7vqfsn92ayh "$query_params" 16 | 17 | echo "feeabs contract: " 18 | echo $CONTRACT 19 | 20 | osmosisd q wasm contract-state smart osmo17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgs5yczr8 '{"get_route":{"input_denom":"ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878","output_denom":"uosmo"}}' 21 | hermes --config scripts/relayer_hermes/config.toml create channel --a-chain testing --b-chain feeappd-t1 --a-port wasm.osmo17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgs5yczr8 --b-port feeabs --new-client-connection --yes 22 | 23 | #osmosisd tx wasm store scripts/bytecode/ibc_stargate-aarch64.wasm --keyring-backend=test --home=$HOME/.osmosisd --from validator1 --chain-id testing --gas 10000000 --fees 25000stake --yes 24 | #INIT='{"packet_lifetime":100}' 25 | #osmosisd tx wasm instantiate 1 "$INIT" --keyring-backend=test --home=$HOME/.osmosisd --from deployer --chain-id testing --label "test" --no-admin --yes 26 | #hermes --config scripts/relayer_hermes/config.toml create channel --a-chain testing --b-chain feeappd-t1 --a-port wasm.osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9 --b-port feeabs --new-client-connection --yes 27 | #osmosisd tx gamm create-pool --pool-file scripts/pool.json --from validator1 --keyring-backend=test --home=$HOME/.osmosisd --chain-id testing --yes 28 | #feeappd tx feeabs interchain-query osmo1ekqk6ms4fqf2mfeazju4pcu3jq93lcdsfl0tah --keyring-backend test --chain-id feeappd-t1 --from feeacc 29 | #feeappd tx feeabs query-osmosis-twap uatom 1676778631 --keyring-backend test --chain-id feeappd-t1 --from feeacc -------------------------------------------------------------------------------- /scripts/fee_abstraction.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/scripts/fee_abstraction.wasm -------------------------------------------------------------------------------- /scripts/host_zone.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Add Fee Abbtraction Host Zone Proposal", 3 | "description": "Add Fee Abbtraction Host Zone", 4 | "host_chain_fee_abs_config": 5 | { 6 | "ibc_denom": "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", 7 | "osmosis_pool_token_denom_in": "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", 8 | "pool_id": "1", 9 | "frozen": false 10 | }, 11 | "deposit": "100000000stake" 12 | } -------------------------------------------------------------------------------- /scripts/host_zone_gaia.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Add Fee Abbtraction Host Zone Proposal", 3 | "description": "Add Fee Abbtraction Host Zone", 4 | "host_chain_fee_abs_config": 5 | { 6 | "ibc_denom": "ibc/9117A26BA81E29FA4F78F57DC2BD90CD3D26848101BA880445F119B22A1E254E", 7 | "osmosis_pool_token_denom_in": "ibc/9117A26BA81E29FA4F78F57DC2BD90CD3D26848101BA880445F119B22A1E254E", 8 | "pool_id": "1", 9 | "frozen": false 10 | }, 11 | "deposit": "100000000stake" 12 | } -------------------------------------------------------------------------------- /scripts/host_zone_query.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Add Fee Abbtraction Host Zone Proposal", 3 | "description": "Add Fee Abbtraction Host Zone", 4 | "host_chain_fee_abs_config": 5 | { 6 | "ibc_denom": "uatom", 7 | "osmosis_pool_token_denom_in": "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878", 8 | "middleware_address": "", 9 | "ibc_transfer_channel":"channel-1", 10 | "host_zone_ibc_transfer_channel":"", 11 | "crosschain_swap_address":"osmo1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqvlx82r", 12 | "pool_id": "1", 13 | "is_osmosis": true, 14 | "frozen": false, 15 | "osmosis_query_channel": "channel-0" 16 | }, 17 | "deposit": "100000000stake" 18 | } -------------------------------------------------------------------------------- /scripts/ibc_swap/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "modify_chain_channel_links": 3 | { 4 | "operations": 5 | [ 6 | {"operation": "set","source_chain": "feeappd-t1","destination_chain": "osmosis","channel_id": "channel-0"}, 7 | {"operation": "set","source_chain": "osmosis","destination_chain": "feeappd-t1","channel_id": "channel-0"}, 8 | {"operation": "set","source_chain": "feeappd-t1","destination_chain": "gaiad-t1","channel_id": "channel-2"}, 9 | {"operation": "set","source_chain": "gaiad-t1","destination_chain": "feeappd-t1","channel_id": "channel-0"}, 10 | {"operation": "set","source_chain": "osmosis","destination_chain": "gaiad-t1","channel_id": "channel-2"}, 11 | {"operation": "set","source_chain": "gaiad-t1","destination_chain": "osmosis","channel_id": "channel-1"} 12 | ] 13 | } 14 | } 15 | 16 | { 17 | "modify_bech32_prefixes": 18 | { 19 | "operations": 20 | [ 21 | {"operation": "set", "chain_name": "feeappd-t1", "prefix": "feeabs"}, 22 | {"operation": "set", "chain_name": "osmosis", "prefix": "osmo"}, 23 | {"operation": "set", "chain_name": "gaiad-t1", "prefix": "cosmos"} 24 | ] 25 | } 26 | } 27 | 28 | { 29 | "wasm": 30 | { 31 | "contract":"'$CROSSCHAIN_SWAPS_ADDRESS'", 32 | "msg": 33 | { 34 | "osmosis_swap": 35 | { 36 | "output_denom":"ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878", 37 | "slippage": 38 | { 39 | "twap": 40 | { 41 | "slippage_percentage":"20", 42 | "window_seconds":10 43 | } 44 | }, 45 | "receiver":"feeappd-t1/feeabs1efd63aw40lxf3n4mhf7dzhjkr453axurwrhrrw", 46 | "on_failed_delivery":"do_nothing", 47 | "next_memo":{} 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /scripts/node_start/runnode_custom.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Run this script to quickly install, setup, and run the current version of juno without docker. 3 | # ./scripts/test_node.sh [clean|c] 4 | 5 | KEY="feeapp1" 6 | CHAINID="feeappd-t1" 7 | MONIKER="localfeeappd" 8 | KEYALGO="secp256k1" 9 | KEYRING="test" 10 | LOGL="info" 11 | 12 | feeappd config keyring-backend $KEYRING 13 | feeappd config chain-id $CHAINID 14 | 15 | command -v feeappd > /dev/null 2>&1 || { echo >&2 "feeappd command not found. Ensure this is setup / properly installed in your GOPATH."; exit 1; } 16 | command -v jq > /dev/null 2>&1 || { echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"; exit 1; } 17 | 18 | from_scratch () { 19 | 20 | make install 21 | 22 | # remove existing daemon. 23 | rm -rf ~/.feeappd/* 24 | 25 | # juno1efd63aw40lxf3n4mhf7dzhjkr453axurv2zdzk 26 | echo "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" | feeappd keys add $KEY --keyring-backend $KEYRING --algo $KEYALGO --recover 27 | # juno1hj5fveer5cjtn4wd6wstzugjfdxzl0xps73ftl 28 | echo "wealth flavor believe regret funny network recall kiss grape useless pepper cram hint member few certain unveil rather brick bargain curious require crowd raise" | feeappd keys add feeacc --keyring-backend $KEYRING --algo $KEYALGO --recover 29 | 30 | feeappd init $MONIKER --chain-id $CHAINID 31 | 32 | # Function updates the config based on a jq argument as a string 33 | update_test_genesis () { 34 | # update_test_genesis '.consensus_params["block"]["max_gas"]="100000000"' 35 | cat $HOME/.feeappd/config/genesis.json | jq "$1" > $HOME/.feeappd/config/tmp_genesis.json && mv $HOME/.feeappd/config/tmp_genesis.json $HOME/.feeappd/config/genesis.json 36 | } 37 | 38 | # Set gas limit in genesis 39 | update_test_genesis '.consensus_params["block"]["max_gas"]="100000000"' 40 | update_test_genesis '.app_state["gov"]["voting_params"]["voting_period"]="45s"' 41 | 42 | update_test_genesis '.app_state["staking"]["params"]["bond_denom"]="stake"' 43 | update_test_genesis '.app_state["bank"]["params"]["send_enabled"]=[{"denom": "stake","enabled": true}]' 44 | # update_test_genesis '.app_state["staking"]["params"]["min_commission_rate"]="0.100000000000000000"' # sdk 46 only 45 | 46 | update_test_genesis '.app_state["mint"]["params"]["mint_denom"]="stake"' 47 | update_test_genesis '.app_state["gov"]["deposit_params"]["min_deposit"]=[{"denom": "stake","amount": "1000000"}]' 48 | update_test_genesis '.app_state["crisis"]["constant_fee"]={"denom": "stake","amount": "1000"}' 49 | 50 | update_test_genesis '.app_state["tokenfactory"]["params"]["denom_creation_fee"]=[{"denom":"stake","amount":"100"}]' 51 | 52 | update_test_genesis '.app_state["feeshare"]["params"]["allowed_denoms"]=["stake"]' 53 | 54 | # Allocate genesis accounts 55 | feeappd add-genesis-account $KEY 10000000000000stake,100000000000000utest --keyring-backend $KEYRING 56 | feeappd add-genesis-account feeacc 10000000000000stake,100000000000000utest --keyring-backend $KEYRING 57 | 58 | feeappd gentx $KEY 10000000000000stake --keyring-backend $KEYRING --chain-id $CHAINID 59 | 60 | # Collect genesis tx 61 | feeappd collect-gentxs 62 | 63 | # Run this to ensure junorything worked and that the genesis file is setup correctly 64 | feeappd validate-genesis 65 | } 66 | 67 | 68 | if [ $# -eq 1 ] && [ $1 == "clean" ] || [ $1 == "c" ]; then 69 | echo "Starting from a clean state" 70 | from_scratch 71 | fi 72 | 73 | echo "Starting node..." 74 | 75 | # Opens the RPC endpoint to outside connections 76 | sed -i '/laddr = "tcp:\/\/127.0.0.1:26657"/c\laddr = "tcp:\/\/0.0.0.0:26657"' ~/.feeappd/config/config.toml 77 | sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["\*"\]/g' ~/.feeappd/config/config.toml 78 | sed -i 's/enable = false/enable = true/g' ~/.feeappd/config/app.toml 79 | sed -i '/address = "tcp:\/\/0.0.0.0:1317"/c\address = "tcp:\/\/0.0.0.0:1318"' ~/.feeappd/config/app.toml 80 | 81 | feeappd config node tcp://0.0.0.0:2241 82 | feeappd start --pruning=nothing --minimum-gas-prices=0.0001stake --p2p.laddr tcp://0.0.0.0:2240 --rpc.laddr tcp://0.0.0.0:2241 --grpc.address 0.0.0.0:2242 --grpc-web.address 0.0.0.0:2243 -------------------------------------------------------------------------------- /scripts/node_start/runnode_gaia.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Run this script to quickly install, setup, and run the current version of juno without docker. 3 | # ./scripts/test_node.sh [clean|c] 4 | 5 | KEY="gaiaapp1" 6 | CHAINID="gaiad-t1" 7 | MONIKER="localgaiad" 8 | KEYALGO="secp256k1" 9 | KEYRING="test" 10 | 11 | gaiad config keyring-backend $KEYRING 12 | gaiad config chain-id $CHAINID 13 | 14 | command -v gaiad > /dev/null 2>&1 || { echo >&2 "gaiad command not found. Ensure this is setup / properly installed in your GOPATH."; exit 1; } 15 | command -v jq > /dev/null 2>&1 || { echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"; exit 1; } 16 | 17 | from_scratch () { 18 | 19 | make install 20 | 21 | # remove existing daemon. 22 | rm -rf ~/.gaia/* 23 | 24 | # juno1efd63aw40lxf3n4mhf7dzhjkr453axurv2zdzk 25 | echo "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" | gaiad keys add $KEY --keyring-backend $KEYRING --algo $KEYALGO --recover 26 | # juno1hj5fveer5cjtn4wd6wstzugjfdxzl0xps73ftl 27 | echo "cup pencil conduct depth analyst human trick excite gain copy option arena mix stamp team soon embody jewel erupt advice access prefer negative cost" | gaiad keys add gnad --keyring-backend $KEYRING --algo $KEYALGO --recover 28 | 29 | gaiad init $MONIKER --chain-id $CHAINID 30 | 31 | # Function updates the config based on a jq argument as a string 32 | update_test_genesis () { 33 | # update_test_genesis '.consensus_params["block"]["max_gas"]="100000000"' 34 | cat $HOME/.gaia/config/genesis.json | jq "$1" > $HOME/.gaia/config/tmp_genesis.json && mv $HOME/.gaia/config/tmp_genesis.json $HOME/.gaia/config/genesis.json 35 | } 36 | 37 | # Set gas limit in genesis 38 | update_test_genesis '.consensus_params["block"]["max_gas"]="100000000"' 39 | update_test_genesis '.app_state["gov"]["voting_params"]["voting_period"]="45s"' 40 | 41 | update_test_genesis '.app_state["staking"]["params"]["bond_denom"]="stake"' 42 | update_test_genesis '.app_state["bank"]["params"]["send_enabled"]=[{"denom": "stake","enabled": true}]' 43 | # update_test_genesis '.app_state["staking"]["params"]["min_commission_rate"]="0.100000000000000000"' # sdk 46 only 44 | 45 | update_test_genesis '.app_state["mint"]["params"]["mint_denom"]="stake"' 46 | update_test_genesis '.app_state["gov"]["deposit_params"]["min_deposit"]=[{"denom": "stake","amount": "1000000"}]' 47 | update_test_genesis '.app_state["crisis"]["constant_fee"]={"denom": "stake","amount": "1000"}' 48 | 49 | update_test_genesis '.app_state["tokenfactory"]["params"]["denom_creation_fee"]=[{"denom":"stake","amount":"100"}]' 50 | 51 | update_test_genesis '.app_state["feeshare"]["params"]["allowed_denoms"]=["stake"]' 52 | 53 | # Allocate genesis accounts 54 | gaiad add-genesis-account $KEY 10000000000000uatom,10000000000000stake,100000000000000utest --keyring-backend $KEYRING 55 | gaiad add-genesis-account gnad 10000000000000uatom,10000000000000stake,100000000000000utest --keyring-backend $KEYRING 56 | 57 | gaiad gentx $KEY 10000000000000stake --keyring-backend $KEYRING --chain-id $CHAINID 58 | 59 | # Collect genesis tx 60 | gaiad collect-gentxs 61 | 62 | # Run this to ensure junorything worked and that the genesis file is setup correctly 63 | gaiad validate-genesis 64 | } 65 | 66 | 67 | if [ $# -eq 1 ] && [ $1 == "clean" ] || [ $1 == "c" ]; then 68 | echo "Starting from a clean state" 69 | from_scratch 70 | fi 71 | 72 | echo "Starting node..." 73 | 74 | gaiad config node tcp://0.0.0.0:3241 75 | gaiad start --pruning=nothing --minimum-gas-prices=0stake --p2p.laddr tcp://0.0.0.0:3240 --rpc.laddr tcp://0.0.0.0:3241 --grpc.address 0.0.0.0:3242 --grpc-web.address 0.0.0.0:3243 -------------------------------------------------------------------------------- /scripts/node_start/runnode_osmosis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # always returns true so set -e doesn't exit if it is not running. 5 | killall osmosisd || true 6 | rm -rf $HOME/.osmosisd/ 7 | 8 | osmosisd config keyring-backend test 9 | 10 | # init all three validators 11 | osmosisd init --chain-id=testing validator1 12 | 13 | # create keys for all three validators 14 | osmosisd keys add validator1 --keyring-backend=test 15 | 16 | update_genesis () { 17 | cat $HOME/.osmosisd/config/genesis.json | jq "$1" > $HOME/.osmosisd/config/tmp_genesis.json && mv $HOME/.osmosisd/config/tmp_genesis.json $HOME/.osmosisd/config/genesis.json 18 | } 19 | echo "lyrics wild earn woman spot rich hen cement trade culture audit amount smoke arm use hollow aerobic correct spirit dolphin tragic all transfer enough" | osmosisd keys add alice --recover --keyring-backend=test 20 | 21 | echo "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" | osmosisd keys add deployer --recover --keyring-backend=test 22 | 23 | # change staking denom to uosmo 24 | update_genesis '.app_state["staking"]["params"]["bond_denom"]="uosmo"' 25 | 26 | # osmo1ekqk6ms4fqf2mfeazju4pcu3jq93lcdsfl0tah 27 | osmosisd add-genesis-account $(osmosisd keys show alice -a --keyring-backend=test ) 1000000000000uosmo,100000000000stake,100000000000uatom,2000000uakt 28 | osmosisd add-genesis-account $(osmosisd keys show deployer -a --keyring-backend=test ) 1000000000000uosmo,100000000000stake,100000000000uatom,2000000uakt 29 | 30 | # create validator node with tokens to transfer to the three other nodes 31 | osmosisd add-genesis-account $(osmosisd keys show validator1 -a --keyring-backend=test ) 1000000000000uosmo,100000000000stake,100000000000uatom,2000000uakt 32 | osmosisd gentx validator1 500000000uosmo --keyring-backend=test --chain-id=testing 33 | osmosisd collect-gentxs 34 | 35 | 36 | # update staking genesis 37 | update_genesis '.app_state["staking"]["params"]["unbonding_time"]="240s"' 38 | 39 | # update crisis variable to uosmo 40 | update_genesis '.app_state["crisis"]["constant_fee"]["denom"]="uosmo"' 41 | 42 | # udpate gov genesis 43 | update_genesis '.app_state["gov"]["voting_params"]["voting_period"]="60s"' 44 | update_genesis '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]="uosmo"' 45 | 46 | # update epochs genesis 47 | update_genesis '.app_state["epochs"]["epochs"][1]["duration"]="60s"' 48 | 49 | # update poolincentives genesis 50 | update_genesis '.app_state["poolincentives"]["lockable_durations"][0]="120s"' 51 | update_genesis '.app_state["poolincentives"]["lockable_durations"][1]="180s"' 52 | update_genesis '.app_state["poolincentives"]["lockable_durations"][2]="240s"' 53 | update_genesis '.app_state["poolincentives"]["params"]["minted_denom"]="uosmo"' 54 | 55 | # update incentives genesis 56 | update_genesis '.app_state["incentives"]["lockable_durations"][0]="1s"' 57 | update_genesis '.app_state["incentives"]["lockable_durations"][1]="120s"' 58 | update_genesis '.app_state["incentives"]["lockable_durations"][2]="180s"' 59 | update_genesis '.app_state["incentives"]["lockable_durations"][3]="240s"' 60 | update_genesis '.app_state["incentives"]["params"]["distr_epoch_identifier"]="day"' 61 | 62 | # update mint genesis 63 | update_genesis '.app_state["mint"]["params"]["mint_denom"]="uosmo"' 64 | update_genesis '.app_state["mint"]["params"]["epoch_identifier"]="day"' 65 | 66 | # update gamm genesis 67 | update_genesis '.app_state["gamm"]["params"]["pool_creation_fee"][0]["denom"]="uosmo"' 68 | 69 | # update interchainquery genesis 70 | update_genesis '.app_state["interchainquery"]["params"]["allow_queries"][0]="/osmosis.twap.v1beta1.Query/ArithmeticTwapToNow"' 71 | 72 | 73 | # port key (validator1 uses default ports) 74 | # validator1 1317, 9090, 9091, 26658, 26657, 26656, 6060 75 | # validator2 1316, 9088, 9089, 26655, 26654, 26653, 6061 76 | # validator3 1315, 9086, 9087, 26652, 26651, 26650, 6062 77 | 78 | # change config.toml values 79 | VALIDATOR1_CONFIG=$HOME/.osmosisd/config/config.toml 80 | 81 | # validator1 82 | sed -i -E 's|allow_duplicate_ip = false|allow_duplicate_ip = true|g' $VALIDATOR1_CONFIG 83 | sed -i -E 's|tcp://127.0.0.1:26658|tcp://0.0.0.0:26658|g' $VALIDATOR1_CONFIG 84 | sed -i -E 's|tcp://127.0.0.1:26657|tcp://0.0.0.0:26657|g' $VALIDATOR1_CONFIG 85 | 86 | # start all three validators 87 | osmosisd start 88 | 89 | echo "1 Validators are up and running!" -------------------------------------------------------------------------------- /scripts/pool.json: -------------------------------------------------------------------------------- 1 | { 2 | "weights": "100uatom,10uosmo", 3 | "initial-deposit": "100uatom,5uosmo", 4 | "swap-fee": "0.01", 5 | "exit-fee": "0.01", 6 | "future-governor": "168h" 7 | } -------------------------------------------------------------------------------- /scripts/proposal.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Enable Fee Abtraction", 3 | "description": "Change params for enable fee abstraction", 4 | "changes": [ 5 | { 6 | "subspace": "feeabs", 7 | "key": "chainname", 8 | "value": "feeabs" 9 | }, 10 | { 11 | "subspace": "feeabs", 12 | "key": "nativeibcedinosmosis", 13 | "value": "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878" 14 | }, 15 | { 16 | "subspace": "feeabs", 17 | "key": "chainname", 18 | "value": "feeabs-2" 19 | }, 20 | { 21 | "subspace": "feeabs", 22 | "key": "ibctransferchannel", 23 | "value": "channel-0" 24 | }, 25 | { 26 | "subspace": "feeabs", 27 | "key": "ibcqueryicqchannel", 28 | "value": "channel-1" 29 | }, 30 | { 31 | "subspace": "feeabs", 32 | "key": "osmosiscrosschainswapaddress", 33 | "value": "osmo17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgs5yczr8" 34 | } 35 | ], 36 | "deposit": "10000000000stake" 37 | } -------------------------------------------------------------------------------- /scripts/proposal_query.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Enable Fee Abtraction", 3 | "description": "Change params for enable fee abstraction", 4 | "changes": [ 5 | 6 | { 7 | "subspace": "feeabs", 8 | "key": "nativeibcedinosmosis", 9 | "value": "uatom" 10 | } 11 | ], 12 | "deposit": "10000000000stake" 13 | } -------------------------------------------------------------------------------- /scripts/protocgen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | # get protoc executions 6 | go get github.com/regen-network/cosmos-proto/protoc-gen-gocosmos 2>/dev/null 7 | 8 | # get cosmos sdk from github 9 | # go get github.com/cosmos/cosmos-sdk 2>/dev/null 10 | 11 | echo "Generating gogo proto code" 12 | cd proto 13 | proto_dirs=$(find ./feeabstraction/absfee/v1beta1/ -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 14 | for dir in $proto_dirs; do 15 | for file in $(find "${dir}" -maxdepth 1 -name '*.proto'); do 16 | if grep go_package $file &>/dev/null; then 17 | buf generate --template buf.gen.gogo.yaml $file 18 | fi 19 | done 20 | done 21 | 22 | cd .. 23 | 24 | # move proto files to the right places 25 | # 26 | # Note: Proto files are suffixed with the current binary version. 27 | cp -r github.com/notional-labs/fee-abstraction/v2/x/feeabs/types/* ./x/feeabs/types/ 28 | rm -rf github.com 29 | 30 | go mod tidy -compat=1.18 31 | -------------------------------------------------------------------------------- /scripts/relayer_hermes/alice.json: -------------------------------------------------------------------------------- 1 | lyrics wild earn woman spot rich hen cement trade culture audit amount smoke arm use hollow aerobic correct spirit dolphin tragic all transfer enough -------------------------------------------------------------------------------- /scripts/relayer_hermes/bob.json: -------------------------------------------------------------------------------- 1 | wealth flavor believe regret funny network recall kiss grape useless pepper cram hint member few certain unveil rather brick bargain curious require crowd raise -------------------------------------------------------------------------------- /scripts/relayer_hermes/gnad.json: -------------------------------------------------------------------------------- 1 | cup pencil conduct depth analyst human trick excite gain copy option arena mix stamp team soon embody jewel erupt advice access prefer negative cost -------------------------------------------------------------------------------- /scripts/run_relayer_feeabs_gaia.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | hermes --config scripts/relayer_hermes/config_feeabs_gaia.toml keys delete --chain gaiad-t1 --all 4 | hermes --config scripts/relayer_hermes/config_feeabs_gaia.toml keys add --chain gaiad-t1 --mnemonic-file scripts/relayer_hermes/gnad.json 5 | 6 | hermes --config scripts/relayer_hermes/config_feeabs_gaia.toml keys delete --chain feeappd-t1 --all 7 | hermes --config scripts/relayer_hermes/config_feeabs_gaia.toml keys add --chain feeappd-t1 --mnemonic-file scripts/relayer_hermes/bob.json 8 | 9 | hermes --config scripts/relayer_hermes/config_feeabs_gaia.toml start 10 | -------------------------------------------------------------------------------- /scripts/run_relayer_feeabs_osmo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | hermes --config scripts/relayer_hermes/config_feeabs_osmosis.toml keys delete --chain testing --all 4 | hermes --config scripts/relayer_hermes/config_feeabs_osmosis.toml keys add --chain testing --mnemonic-file scripts/relayer_hermes/alice.json 5 | 6 | hermes --config scripts/relayer_hermes/config_feeabs_osmosis.toml keys delete --chain feeappd-t1 --all 7 | hermes --config scripts/relayer_hermes/config_feeabs_osmosis.toml keys add --chain feeappd-t1 --mnemonic-file scripts/relayer_hermes/bob.json 8 | 9 | hermes --config scripts/relayer_hermes/config_feeabs_osmosis.toml start 10 | -------------------------------------------------------------------------------- /scripts/run_relayer_osmo_gaia.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | hermes --config scripts/relayer_hermes/config_osmosis_gaia.toml keys delete --chain testing --all 4 | hermes --config scripts/relayer_hermes/config_osmosis_gaia.toml keys add --chain testing --mnemonic-file scripts/relayer_hermes/alice.json 5 | 6 | hermes --config scripts/relayer_hermes/config_osmosis_gaia.toml keys delete --chain gaiad-t1 --all 7 | hermes --config scripts/relayer_hermes/config_osmosis_gaia.toml keys add --chain gaiad-t1 --mnemonic-file scripts/relayer_hermes/gnad.json 8 | 9 | hermes --config scripts/relayer_hermes/config_osmosis_gaia.toml start 10 | -------------------------------------------------------------------------------- /testnode.sh: -------------------------------------------------------------------------------- 1 | KEY="mykey" 2 | CHAINID="test-1" 3 | MONIKER="localtestnet" 4 | KEYALGO="secp256k1" 5 | KEYRING="test" 6 | LOGLEVEL="info" 7 | # to trace evm 8 | #TRACE="--trace" 9 | TRACE="" 10 | 11 | # validate dependencies are installed 12 | command -v jq > /dev/null 2>&1 || { echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"; exit 1; } 13 | 14 | # remove existing daemon 15 | rm -rf ~/.feeapp* 16 | 17 | feeappd config keyring-backend $KEYRING 18 | feeappd config chain-id $CHAINID 19 | 20 | # if $KEY exists it should be deleted 21 | feeappd keys add $KEY --keyring-backend $KEYRING --algo $KEYALGO 22 | 23 | # Set moniker and chain-id for Evmos (Moniker can be anything, chain-id must be an integer) 24 | feeappd init $MONIKER --chain-id $CHAINID 25 | 26 | # Allocate genesis accounts (cosmos formatted addresses) 27 | feeappd add-genesis-account $KEY 100000000000000000000000000stake --keyring-backend $KEYRING 28 | 29 | # Sign genesis transaction 30 | feeappd gentx $KEY 1000000000000000000000stake --keyring-backend $KEYRING --chain-id $CHAINID 31 | 32 | # Collect genesis tx 33 | feeappd collect-gentxs 34 | 35 | # Run this to ensure everything worked and that the genesis file is setup correctly 36 | feeappd validate-genesis 37 | 38 | if [[ $1 == "pending" ]]; then 39 | echo "pending mode is on, please wait for the first block committed." 40 | fi 41 | 42 | # Start the node (remove the --pruning=nothing flag if historical queries are not needed) 43 | feeappd start --pruning=nothing --minimum-gas-prices=0.0001stake --rpc.laddr tcp://127.0.0.1:26650 44 | 45 | -------------------------------------------------------------------------------- /tests/interchaintest/bytecode/crosschain_registry.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/tests/interchaintest/bytecode/crosschain_registry.wasm -------------------------------------------------------------------------------- /tests/interchaintest/bytecode/crosschain_swaps.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/tests/interchaintest/bytecode/crosschain_swaps.wasm -------------------------------------------------------------------------------- /tests/interchaintest/bytecode/swaprouter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/tests/interchaintest/bytecode/swaprouter.wasm -------------------------------------------------------------------------------- /tests/interchaintest/chain_start_test.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/strangelove-ventures/interchaintest/v4" 8 | "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" 9 | "github.com/strangelove-ventures/interchaintest/v4/testreporter" 10 | "github.com/stretchr/testify/require" 11 | "go.uber.org/zap/zaptest" 12 | ) 13 | 14 | // TestStartFeeabs is a basic test to assert that spinning up a Feeabs network with 1 validator works properly. 15 | func TestStartFeeabs(t *testing.T) { 16 | if testing.Short() { 17 | t.Skip() 18 | } 19 | 20 | t.Parallel() 21 | 22 | ctx := context.Background() 23 | 24 | // Create chain factory with Feeabs 25 | numVals := 1 26 | numFullNodes := 1 27 | 28 | cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{ 29 | { 30 | Name: "feeabs", 31 | ChainConfig: feeabsConfig, 32 | NumValidators: &numVals, 33 | NumFullNodes: &numFullNodes, 34 | }, 35 | }) 36 | 37 | // Get chains from the chain factory 38 | chains, err := cf.Chains(t.Name()) 39 | require.NoError(t, err) 40 | 41 | feeabs := chains[0].(*cosmos.CosmosChain) 42 | 43 | // Relayer Factory 44 | client, network := interchaintest.DockerSetup(t) 45 | 46 | // Create a new Interchain object which describes the chains, relayers, and IBC connections we want to use 47 | ic := interchaintest.NewInterchain().AddChain(feeabs) 48 | 49 | rep := testreporter.NewNopReporter() 50 | eRep := rep.RelayerExecReporter(t) 51 | 52 | err = ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{ 53 | TestName: t.Name(), 54 | Client: client, 55 | NetworkID: network, 56 | SkipPathCreation: true, 57 | 58 | // This can be used to write to the block database which will index all block data e.g. txs, msgs, events, etc. 59 | // BlockDatabaseFile: interchaintest.DefaultBlockDatabaseFilepath(), 60 | }) 61 | require.NoError(t, err) 62 | 63 | t.Cleanup(func() { 64 | _ = ic.Close() 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /tests/interchaintest/osmosistypes/gamm/balancer/codec.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/types/msgservice" 8 | ) 9 | 10 | func RegisterInterfaces(registry codectypes.InterfaceRegistry) { 11 | registry.RegisterImplementations( 12 | (*sdk.Msg)(nil), 13 | &MsgCreateBalancerPool{}, 14 | &MsgMigrateSharesToFullRangeConcentratedPosition{}, 15 | ) 16 | 17 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 18 | } 19 | 20 | var ( 21 | amino = codec.NewLegacyAmino() 22 | 23 | // ModuleCdc references the global x/bank module codec. Note, the codec should 24 | // ONLY be used in certain instances of tests and for JSON encoding as Amino is 25 | // still used for that purpose. 26 | // 27 | // The actual codec used for serialization should be provided to x/staking and 28 | // defined at the application level. 29 | ModuleCdc = codec.NewAminoCodec(amino) 30 | ) 31 | -------------------------------------------------------------------------------- /tests/interchaintest/osmosistypes/gamm/balancer/msgs.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/notional-labs/fee-abstraction/tests/interchaintest/osmosistypes/gamm/types" 6 | ) 7 | 8 | const ( 9 | TypeMsgCreateBalancerPool = "create_balancer_pool" 10 | TypeMsgMigrateShares = "migrate_shares" 11 | ) 12 | 13 | var ( 14 | _ sdk.Msg = &MsgCreateBalancerPool{} 15 | ) 16 | 17 | func (msg MsgCreateBalancerPool) Route() string { return types.RouterKey } 18 | func (msg MsgCreateBalancerPool) Type() string { return TypeMsgCreateBalancerPool } 19 | func (msg MsgCreateBalancerPool) ValidateBasic() error { return nil } 20 | 21 | func (msg MsgCreateBalancerPool) GetSignBytes() []byte { 22 | return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) 23 | } 24 | 25 | func (msg MsgCreateBalancerPool) GetSigners() []sdk.AccAddress { 26 | sender, err := sdk.AccAddressFromBech32(msg.Sender) 27 | if err != nil { 28 | panic(err) 29 | } 30 | return []sdk.AccAddress{sender} 31 | } 32 | 33 | var _ sdk.Msg = &MsgMigrateSharesToFullRangeConcentratedPosition{} 34 | 35 | func (msg MsgMigrateSharesToFullRangeConcentratedPosition) Route() string { return types.RouterKey } 36 | func (msg MsgMigrateSharesToFullRangeConcentratedPosition) Type() string { return TypeMsgMigrateShares } 37 | func (msg MsgMigrateSharesToFullRangeConcentratedPosition) ValidateBasic() error { return nil } 38 | 39 | func (msg MsgMigrateSharesToFullRangeConcentratedPosition) GetSignBytes() []byte { 40 | return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) 41 | } 42 | 43 | func (msg MsgMigrateSharesToFullRangeConcentratedPosition) GetSigners() []sdk.AccAddress { 44 | sender, err := sdk.AccAddressFromBech32(msg.Sender) 45 | if err != nil { 46 | panic(err) 47 | } 48 | return []sdk.AccAddress{sender} 49 | } 50 | -------------------------------------------------------------------------------- /tests/interchaintest/osmosistypes/gamm/balancer/pool.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | type balancerPoolPretty struct { 10 | Address sdk.AccAddress `json:"address" yaml:"address"` 11 | Id uint64 `json:"id" yaml:"id"` 12 | PoolParams PoolParams `json:"pool_params" yaml:"pool_params"` 13 | FuturePoolGovernor string `json:"future_pool_governor" yaml:"future_pool_governor"` 14 | TotalWeight sdk.Dec `json:"total_weight" yaml:"total_weight"` 15 | TotalShares sdk.Coin `json:"total_shares" yaml:"total_shares"` 16 | PoolAssets []PoolAsset `json:"pool_assets" yaml:"pool_assets"` 17 | } 18 | 19 | func (p Pool) String() string { 20 | out, err := p.MarshalJSON() 21 | if err != nil { 22 | panic(err) 23 | } 24 | return string(out) 25 | } 26 | 27 | // MarshalJSON returns the JSON representation of a Pool. 28 | func (p Pool) MarshalJSON() ([]byte, error) { 29 | accAddr, err := sdk.AccAddressFromBech32(p.Address) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | decTotalWeight := sdk.NewDecFromInt(p.TotalWeight) 35 | 36 | return json.Marshal(balancerPoolPretty{ 37 | Address: accAddr, 38 | Id: p.Id, 39 | PoolParams: p.PoolParams, 40 | FuturePoolGovernor: p.FuturePoolGovernor, 41 | TotalWeight: decTotalWeight, 42 | TotalShares: p.TotalShares, 43 | PoolAssets: p.PoolAssets, 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /tests/interchaintest/osmosistypes/gamm/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | types "github.com/cosmos/cosmos-sdk/codec/types" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/types/msgservice" 8 | ) 9 | 10 | func RegisterInterfaces(registry types.InterfaceRegistry) { 11 | registry.RegisterImplementations( 12 | (*sdk.Msg)(nil), 13 | &MsgJoinPool{}, 14 | &MsgExitPool{}, 15 | &MsgSwapExactAmountIn{}, 16 | &MsgSwapExactAmountOut{}, 17 | &MsgJoinSwapExternAmountIn{}, 18 | &MsgJoinSwapShareAmountOut{}, 19 | &MsgExitSwapExternAmountOut{}, 20 | &MsgExitSwapShareAmountIn{}, 21 | ) 22 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 23 | } 24 | 25 | var ( 26 | amino = codec.NewLegacyAmino() 27 | 28 | // ModuleCdc references the global x/bank module codec. Note, the codec should 29 | // ONLY be used in certain instances of tests and for JSON encoding as Amino is 30 | // still used for that purpose. 31 | // 32 | // The actual codec used for serialization should be provided to x/staking and 33 | // defined at the application level. 34 | ModuleCdc = codec.NewAminoCodec(amino) 35 | ) 36 | -------------------------------------------------------------------------------- /tests/interchaintest/proposal/host_zone.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Add Fee Abbtraction Host Zone Proposal", 3 | "description": "Add Fee Abbtraction Host Zone", 4 | "host_chain_fee_abs_config": 5 | { 6 | "ibc_denom": "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", 7 | "osmosis_pool_token_denom_in": "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", 8 | "pool_id": "1", 9 | "frozen": false 10 | }, 11 | "deposit": "100000000stake" 12 | } -------------------------------------------------------------------------------- /tests/interchaintest/proposal/proposal.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Enable Fee Abtraction", 3 | "description": "Change params for enable fee abstraction", 4 | "changes": [ 5 | { 6 | "subspace": "feeabs", 7 | "key": "nativeibcedinosmosis", 8 | "value": "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878" 9 | }, 10 | { 11 | "subspace": "feeabs", 12 | "key": "chainname", 13 | "value": "feeabs" 14 | }, 15 | { 16 | "subspace": "feeabs", 17 | "key": "ibctransferchannel", 18 | "value": "channel-0" 19 | }, 20 | { 21 | "subspace": "feeabs", 22 | "key": "ibcqueryicqchannel", 23 | "value": "channel-1" 24 | }, 25 | { 26 | "subspace": "feeabs", 27 | "key": "osmosiscrosschainswapaddress", 28 | "value": "osmo17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgs5yczr8" 29 | } 30 | ], 31 | "deposit": "5000000000stake" 32 | } -------------------------------------------------------------------------------- /tests/interchaintest/setup.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | simappparams "github.com/cosmos/cosmos-sdk/simapp/params" 8 | "github.com/cosmos/cosmos-sdk/types" 9 | balancertypes "github.com/notional-labs/fee-abstraction/tests/interchaintest/osmosistypes/gamm/balancer" 10 | gammtypes "github.com/notional-labs/fee-abstraction/tests/interchaintest/osmosistypes/gamm/types" 11 | feeabstype "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 12 | "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" 13 | "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos/wasm" 14 | "github.com/strangelove-ventures/interchaintest/v4/ibc" 15 | ) 16 | 17 | type QueryFeeabsModuleBalacesResponse struct { 18 | Balances types.Coins 19 | Address string 20 | } 21 | 22 | type QueryHostChainConfigRespone struct { 23 | HostChainConfig cosmos.HostChainFeeAbsConfig `protobuf:"bytes,1,opt,name=host_chain_config,json=hostChainConfig,proto3" json:"host_chain_config" yaml:"host_chain_config"` 24 | } 25 | 26 | const ( 27 | votingPeriod = "10s" 28 | maxDepositPeriod = "10s" 29 | ) 30 | 31 | var ( 32 | FeeabsMainRepo = "ghcr.io/notional-labs/fee-abstraction" 33 | FeeabsICTestRepo = "ghcr.io/notional-labs/fee-abstraction-ictest" 34 | 35 | repo, version = GetDockerImageInfo() 36 | 37 | feeabsImage = ibc.DockerImage{ 38 | Repository: repo, 39 | Version: version, 40 | UidGid: "1025:1025", 41 | } 42 | 43 | feeabsConfig = ibc.ChainConfig{ 44 | Type: "cosmos", 45 | Name: "feeabs", 46 | ChainID: "feeabs-2", 47 | Images: []ibc.DockerImage{feeabsImage}, 48 | Bin: "feeappd", 49 | Bech32Prefix: "feeabs", 50 | Denom: "stake", 51 | CoinType: "118", 52 | GasPrices: "0.0stake", 53 | GasAdjustment: 1.1, 54 | TrustingPeriod: "112h", 55 | NoHostMount: false, 56 | SkipGenTx: false, 57 | PreGenesis: nil, 58 | ModifyGenesis: cosmos.ModifyGenesisProposalTime(votingPeriod, maxDepositPeriod), 59 | ConfigFileOverrides: nil, 60 | EncodingConfig: feeabsEncoding(), 61 | } 62 | 63 | pathFeeabsGaia = "feeabs-gaia" 64 | pathFeeabsOsmosis = "feeabs-osmosis" 65 | pathOsmosisGaia = "osmosis-gaia" 66 | genesisWalletAmount = int64(10_000_000) 67 | ) 68 | 69 | // feeabsEncoding registers the feeabs specific module codecs so that the associated types and msgs 70 | // will be supported when writing to the blocksdb sqlite database. 71 | func feeabsEncoding() *simappparams.EncodingConfig { 72 | cfg := wasm.WasmEncoding() 73 | 74 | // register custom types 75 | feeabstype.RegisterInterfaces(cfg.InterfaceRegistry) 76 | 77 | return cfg 78 | } 79 | 80 | func osmosisEncoding() *simappparams.EncodingConfig { 81 | cfg := wasm.WasmEncoding() 82 | 83 | gammtypes.RegisterInterfaces(cfg.InterfaceRegistry) 84 | balancertypes.RegisterInterfaces(cfg.InterfaceRegistry) 85 | 86 | return cfg 87 | } 88 | 89 | // GetDockerImageInfo returns the appropriate repo and branch version string for integration with the CI pipeline. 90 | // The remote runner sets the BRANCH_CI env var. If present, interchaintest will use the docker image pushed up to the repo. 91 | // If testing locally, user should run `make docker-build-debug` and interchaintest will use the local image. 92 | func GetDockerImageInfo() (repo, version string) { 93 | branchVersion, found := os.LookupEnv("BRANCH_CI") 94 | repo = FeeabsICTestRepo 95 | if !found { 96 | // make local-image 97 | repo = "feeapp" 98 | branchVersion = "debug" 99 | } 100 | 101 | // github converts / to - for pushed docker images 102 | branchVersion = strings.ReplaceAll(branchVersion, "/", "-") 103 | return repo, branchVersion 104 | } 105 | -------------------------------------------------------------------------------- /x/feeabs/ante/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package ante 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/x/auth/types" 6 | ) 7 | 8 | type FeegrantKeeper interface { 9 | UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error 10 | } 11 | 12 | type AccountKeeper interface { 13 | GetParams(ctx sdk.Context) (params types.Params) 14 | GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI 15 | SetAccount(ctx sdk.Context, acc types.AccountI) 16 | GetModuleAddress(moduleName string) sdk.AccAddress 17 | } 18 | 19 | type BankKeeper interface { 20 | SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 21 | } 22 | -------------------------------------------------------------------------------- /x/feeabs/client/cli/query.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cosmos/cosmos-sdk/client" 7 | "github.com/cosmos/cosmos-sdk/client/flags" 8 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func GetQueryCmd() *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: types.ModuleName, 15 | Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), 16 | DisableFlagParsing: true, 17 | SuggestionsMinimumDistance: 2, 18 | RunE: client.ValidateCmd, 19 | } 20 | 21 | cmd.AddCommand( 22 | GetCmdQueryOsmosisArithmeticTwap(), 23 | GetCmdQueryFeeabsModuleBalances(), 24 | GetCmdQueryHostChainConfig(), 25 | GetCmdQueryAllHostChainConfig(), 26 | ) 27 | 28 | return cmd 29 | } 30 | 31 | func GetCmdQueryOsmosisArithmeticTwap() *cobra.Command { 32 | cmd := &cobra.Command{ 33 | Use: "osmo-arithmetic-twap [ibc-denom]", 34 | Args: cobra.ExactArgs(1), 35 | Short: "Query the arithmetic twap of osmosis", 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | clientCtx, err := client.GetClientQueryContext(cmd) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | queryClient := types.NewQueryClient(clientCtx) 43 | 44 | req := &types.QueryOsmosisArithmeticTwapRequest{ 45 | IbcDenom: args[0], 46 | } 47 | 48 | res, err := queryClient.OsmosisArithmeticTwap(cmd.Context(), req) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | return clientCtx.PrintProto(res) 54 | }, 55 | } 56 | flags.AddQueryFlagsToCmd(cmd) 57 | 58 | return cmd 59 | } 60 | 61 | func GetCmdQueryFeeabsModuleBalances() *cobra.Command { 62 | cmd := &cobra.Command{ 63 | Use: "module-balances", 64 | Args: cobra.NoArgs, 65 | Short: "Query feeabs module balances", 66 | RunE: func(cmd *cobra.Command, args []string) error { 67 | clientCtx, err := client.GetClientQueryContext(cmd) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | queryClient := types.NewQueryClient(clientCtx) 73 | 74 | res, err := queryClient.FeeabsModuleBalances(cmd.Context(), &types.QueryFeeabsModuleBalacesRequest{}) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | return clientCtx.PrintProto(res) 80 | }, 81 | } 82 | flags.AddQueryFlagsToCmd(cmd) 83 | 84 | return cmd 85 | } 86 | 87 | func GetCmdQueryHostChainConfig() *cobra.Command { 88 | cmd := &cobra.Command{ 89 | Use: "host-chain-config [ibc-denom]", 90 | Args: cobra.ExactArgs(1), 91 | Short: "Query host chain config", 92 | RunE: func(cmd *cobra.Command, args []string) error { 93 | clientCtx, err := client.GetClientQueryContext(cmd) 94 | if err != nil { 95 | return err 96 | } 97 | 98 | queryClient := types.NewQueryClient(clientCtx) 99 | 100 | req := &types.QueryHostChainConfigRequest{ 101 | IbcDenom: args[0], 102 | } 103 | 104 | res, err := queryClient.HostChainConfig(cmd.Context(), req) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | return clientCtx.PrintProto(res) 110 | }, 111 | } 112 | flags.AddQueryFlagsToCmd(cmd) 113 | 114 | return cmd 115 | } 116 | func GetCmdQueryAllHostChainConfig() *cobra.Command { 117 | cmd := &cobra.Command{ 118 | Use: "all-host-chain-config [ibc-denom]", 119 | Args: cobra.ExactArgs(0), 120 | Short: "Query all host chain config", 121 | RunE: func(cmd *cobra.Command, args []string) error { 122 | clientCtx, err := client.GetClientQueryContext(cmd) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | queryClient := types.NewQueryClient(clientCtx) 128 | 129 | req := &types.AllQueryHostChainConfigRequest{} 130 | 131 | res, err := queryClient.AllHostChainConfig(cmd.Context(), req) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | return clientCtx.PrintProto(res) 137 | }, 138 | } 139 | flags.AddQueryFlagsToCmd(cmd) 140 | 141 | return cmd 142 | } 143 | -------------------------------------------------------------------------------- /x/feeabs/client/cli/tx_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | "github.com/cosmos/cosmos-sdk/testutil" 8 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestParseProposal(t *testing.T) { 13 | expectedConfig := types.HostChainFeeAbsConfig{ 14 | IbcDenom: "ibc/123", 15 | OsmosisPoolTokenDenomIn: "ibc/456", 16 | PoolId: 1, 17 | Frozen: false, 18 | } 19 | cdc := codec.NewLegacyAmino() 20 | okJSON := testutil.WriteToNewTempFile(t, ` 21 | { 22 | "title": "Add Fee Abbtraction Host Zone Proposal", 23 | "description": "Add Fee Abbtraction Host Zone", 24 | "host_chain_fee_abs_config": 25 | { 26 | "ibc_denom": "ibc/123", 27 | "osmosis_pool_token_denom_in": "ibc/456", 28 | "middleware_address": "cosmos123", 29 | "ibc_transfer_channel":"channel-1", 30 | "host_zone_ibc_transfer_channel":"channel-2", 31 | "crosschain_swap_address":"osmo123456", 32 | "pool_id": "1", 33 | "is_osmosis": false, 34 | "frozen": false, 35 | "osmosis_query_channel": "channel-3" 36 | 37 | }, 38 | "deposit": "1000stake" 39 | } 40 | `) 41 | 42 | proposal, err := ParseAddHostZoneProposalJSON(cdc, okJSON.Name()) 43 | require.NoError(t, err) 44 | require.Equal(t, "Add Fee Abbtraction Host Zone Proposal", proposal.Title) 45 | require.Equal(t, "Add Fee Abbtraction Host Zone", proposal.Description) 46 | require.Equal(t, "1000stake", proposal.Deposit) 47 | require.Equal(t, expectedConfig, proposal.HostChainFeeAbsConfig) 48 | } 49 | -------------------------------------------------------------------------------- /x/feeabs/client/cli/util.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "io/ioutil" //nolint:staticcheck 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 8 | ) 9 | 10 | // ParseParamChangeProposalJSON reads and parses a ParamChangeProposalJSON from 11 | // file. 12 | func ParseAddHostZoneProposalJSON(cdc *codec.LegacyAmino, proposalFile string) (AddHostZoneProposalJSON, error) { 13 | proposal := AddHostZoneProposalJSON{} 14 | 15 | contents, err := ioutil.ReadFile(proposalFile) 16 | if err != nil { 17 | return proposal, err 18 | } 19 | 20 | if err := cdc.UnmarshalJSON(contents, &proposal); err != nil { 21 | return proposal, err 22 | } 23 | 24 | return proposal, nil 25 | } 26 | 27 | func ParseDeleteHostZoneProposalJSON(cdc *codec.LegacyAmino, proposalFile string) (DeleteHostZoneProposalJSON, error) { 28 | proposal := DeleteHostZoneProposalJSON{} 29 | 30 | contents, err := ioutil.ReadFile(proposalFile) 31 | if err != nil { 32 | return proposal, err 33 | } 34 | 35 | if err := cdc.UnmarshalJSON(contents, &proposal); err != nil { 36 | return proposal, err 37 | } 38 | 39 | return proposal, nil 40 | } 41 | 42 | func ParseSetHostZoneProposalJSON(cdc *codec.LegacyAmino, proposalFile string) (SetHostZoneProposalJSON, error) { 43 | proposal := SetHostZoneProposalJSON{} 44 | 45 | contents, err := ioutil.ReadFile(proposalFile) 46 | if err != nil { 47 | return proposal, err 48 | } 49 | 50 | if err := cdc.UnmarshalJSON(contents, &proposal); err != nil { 51 | return proposal, err 52 | } 53 | 54 | return proposal, nil 55 | } 56 | 57 | type ( 58 | AddHostZoneProposalJSON struct { 59 | Title string `json:"title" yaml:"title"` 60 | Description string `json:"description" yaml:"description"` 61 | HostChainFeeAbsConfig types.HostChainFeeAbsConfig `json:"host_chain_fee_abs_config" yaml:"host_chain_fee_abs_config"` 62 | Deposit string `json:"deposit" yaml:"deposit"` 63 | } 64 | DeleteHostZoneProposalJSON struct { 65 | Title string `json:"title" yaml:"title"` 66 | Description string `json:"description" yaml:"description"` 67 | IbcDenom string `json:"ibc_denom" yaml:"ibc_denom"` 68 | Deposit string `json:"deposit" yaml:"deposit"` 69 | } 70 | SetHostZoneProposalJSON struct { 71 | Title string `json:"title" yaml:"title"` 72 | Description string `json:"description" yaml:"description"` 73 | HostChainFeeAbsConfig types.HostChainFeeAbsConfig `json:"host_chain_fee_abs_config" yaml:"host_chain_fee_abs_config"` 74 | Deposit string `json:"deposit" yaml:"deposit"` 75 | } 76 | ) 77 | -------------------------------------------------------------------------------- /x/feeabs/ibctesting/README.md: -------------------------------------------------------------------------------- 1 | # testing package for ibc 2 | Customized version of cosmos-sdk x/ibc/testing and CosmWasm wasmd x/wasm/ibctesting -------------------------------------------------------------------------------- /x/feeabs/ibctesting/event_utils.go: -------------------------------------------------------------------------------- 1 | package ibctesting 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" 8 | channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" 9 | abci "github.com/tendermint/tendermint/abci/types" 10 | ) 11 | 12 | func getSendPackets(evts []abci.Event) []channeltypes.Packet { 13 | var res []channeltypes.Packet 14 | for _, evt := range evts { 15 | if evt.Type == "send_packet" { 16 | packet := parsePacketFromEvent(evt) 17 | res = append(res, packet) 18 | } 19 | } 20 | return res 21 | } 22 | 23 | func getAckPackets(evts []abci.Event) []PacketAck { 24 | var res []PacketAck 25 | for _, evt := range evts { 26 | if evt.Type == "write_acknowledgement" { 27 | packet := parsePacketFromEvent(evt) 28 | ack := PacketAck{ 29 | Packet: packet, 30 | Ack: []byte(getField(evt, "packet_ack")), 31 | } 32 | res = append(res, ack) 33 | } 34 | } 35 | return res 36 | } 37 | 38 | // Used for various debug statements above when needed... do not remove 39 | // func showEvent(evt abci.Event) { 40 | // fmt.Printf("evt.Type: %s\n", evt.Type) 41 | // for _, attr := range evt.Attributes { 42 | // fmt.Printf(" %s = %s\n", string(attr.Key), string(attr.Value)) 43 | // } 44 | //} 45 | 46 | func parsePacketFromEvent(evt abci.Event) channeltypes.Packet { 47 | return channeltypes.Packet{ 48 | Sequence: getUintField(evt, "packet_sequence"), 49 | SourcePort: getField(evt, "packet_src_port"), 50 | SourceChannel: getField(evt, "packet_src_channel"), 51 | DestinationPort: getField(evt, "packet_dst_port"), 52 | DestinationChannel: getField(evt, "packet_dst_channel"), 53 | Data: []byte(getField(evt, "packet_data")), 54 | TimeoutHeight: parseTimeoutHeight(getField(evt, "packet_timeout_height")), 55 | TimeoutTimestamp: getUintField(evt, "packet_timeout_timestamp"), 56 | } 57 | } 58 | 59 | // return the value for the attribute with the given name 60 | func getField(evt abci.Event, key string) string { 61 | for _, attr := range evt.Attributes { 62 | if string(attr.Key) == key { 63 | return string(attr.Value) 64 | } 65 | } 66 | return "" 67 | } 68 | 69 | func getUintField(evt abci.Event, key string) uint64 { 70 | raw := getField(evt, key) 71 | return toUint64(raw) 72 | } 73 | 74 | func toUint64(raw string) uint64 { 75 | if raw == "" { 76 | return 0 77 | } 78 | i, err := strconv.ParseUint(raw, 10, 64) 79 | if err != nil { 80 | panic(err) 81 | } 82 | return i 83 | } 84 | 85 | func parseTimeoutHeight(raw string) clienttypes.Height { 86 | chunks := strings.Split(raw, "-") 87 | return clienttypes.Height{ 88 | RevisionNumber: toUint64(chunks[0]), 89 | RevisionHeight: toUint64(chunks[1]), 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /x/feeabs/ibctesting/path.go: -------------------------------------------------------------------------------- 1 | package ibctesting 2 | 3 | import ( 4 | ibctesting "github.com/cosmos/ibc-go/v4/testing" 5 | ) 6 | 7 | // Path contains two endpoints representing two chains connected over IBC 8 | type Path struct { 9 | EndpointA *Endpoint 10 | EndpointB *Endpoint 11 | } 12 | 13 | // NewPath constructs an endpoint for each chain using the default values 14 | // for the endpoints. Each endpoint is updated to have a pointer to the 15 | // counterparty endpoint. 16 | func NewPath(chainA, chainB *TestChain) *Path { 17 | endpointA := NewDefaultEndpoint(chainA) 18 | endpointB := NewDefaultEndpoint(chainB) 19 | 20 | endpointA.Counterparty = endpointB 21 | endpointB.Counterparty = endpointA 22 | 23 | return &Path{ 24 | EndpointA: endpointA, 25 | EndpointB: endpointB, 26 | } 27 | } 28 | 29 | // NewDefaultEndpoint constructs a new endpoint using default values. 30 | // CONTRACT: the counterparty endpoitn must be set by the caller. 31 | func NewDefaultEndpoint(chain *TestChain) *Endpoint { 32 | return &Endpoint{ 33 | Chain: chain, 34 | ClientConfig: ibctesting.NewTendermintConfig(), 35 | ConnectionConfig: ibctesting.NewConnectionConfig(), 36 | ChannelConfig: ibctesting.NewChannelConfig(), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /x/feeabs/ibctesting/wasm.go: -------------------------------------------------------------------------------- 1 | package ibctesting 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/CosmWasm/wasmd/x/wasm/types" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | ibctesting "github.com/cosmos/ibc-go/v4/testing" 9 | "github.com/golang/protobuf/proto" //nolint 10 | feeabs "github.com/notional-labs/fee-abstraction/v2/app" 11 | "github.com/stretchr/testify/require" 12 | "github.com/tendermint/tendermint/libs/rand" 13 | ) 14 | 15 | var wasmIdent = []byte("\x00\x61\x73\x6D") 16 | 17 | // SeedNewContractInstance stores some wasm code and instantiates a new contract on this chain. 18 | // This method can be called to prepare the store with some valid CodeInfo and ContractInfo. The returned 19 | // Address is the contract address for this instance. Test should make use of this data and/or use NewIBCContractMockWasmer 20 | // for using a contract mock in Go. 21 | func (chain *TestChain) SeedNewContractInstance() sdk.AccAddress { 22 | pInstResp := chain.StoreCode(append(wasmIdent, rand.Bytes(10)...)) 23 | codeID := pInstResp.CodeID 24 | 25 | anyAddressStr := chain.SenderAccount.GetAddress().String() 26 | initMsg := []byte(fmt.Sprintf(`{"verifier": %q, "beneficiary": %q}`, anyAddressStr, anyAddressStr)) 27 | return chain.InstantiateContract(codeID, initMsg) 28 | } 29 | 30 | func (chain *TestChain) StoreCode(byteCode []byte) types.MsgStoreCodeResponse { 31 | storeMsg := &types.MsgStoreCode{ 32 | Sender: chain.SenderAccount.GetAddress().String(), 33 | WASMByteCode: byteCode, 34 | } 35 | r, err := chain.SendMsgs(storeMsg) 36 | require.NoError(chain.t, err) 37 | protoResult := chain.parseSDKResultData(r) 38 | require.Len(chain.t, protoResult.Data, 1) 39 | // unmarshal protobuf response from data 40 | var pInstResp types.MsgStoreCodeResponse 41 | require.NoError(chain.t, pInstResp.Unmarshal(protoResult.Data[0].Data)) 42 | require.NotEmpty(chain.t, pInstResp.CodeID) 43 | return pInstResp 44 | } 45 | 46 | func (chain *TestChain) InstantiateContract(codeID uint64, initMsg []byte) sdk.AccAddress { 47 | instantiateMsg := &types.MsgInstantiateContract{ 48 | Sender: chain.SenderAccount.GetAddress().String(), 49 | Admin: chain.SenderAccount.GetAddress().String(), 50 | CodeID: codeID, 51 | Label: "ibc-test", 52 | Msg: initMsg, 53 | Funds: sdk.Coins{ibctesting.TestCoin}, 54 | } 55 | 56 | r, err := chain.SendMsgs(instantiateMsg) 57 | require.NoError(chain.t, err) 58 | protoResult := chain.parseSDKResultData(r) 59 | require.Len(chain.t, protoResult.Data, 1) 60 | 61 | var pExecResp types.MsgInstantiateContractResponse 62 | require.NoError(chain.t, pExecResp.Unmarshal(protoResult.Data[0].Data)) 63 | a, err := sdk.AccAddressFromBech32(pExecResp.Address) 64 | require.NoError(chain.t, err) 65 | return a 66 | } 67 | 68 | func (chain *TestChain) parseSDKResultData(r *sdk.Result) sdk.TxMsgData { 69 | var protoResult sdk.TxMsgData 70 | require.NoError(chain.t, proto.Unmarshal(r.Data, &protoResult)) 71 | return protoResult 72 | } 73 | 74 | // ContractInfo is a helper function to returns the ContractInfo for the given contract address 75 | func (chain *TestChain) ContractInfo(contractAddr sdk.AccAddress) *types.ContractInfo { 76 | type testSupporter interface { 77 | TestSupport() *feeabs.TestSupport 78 | } 79 | return chain.App.(testSupporter).TestSupport().WasmKeeper().GetContractInfo(chain.GetContext(), contractAddr) 80 | } 81 | -------------------------------------------------------------------------------- /x/feeabs/keeper/abci.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/cosmos/cosmos-sdk/telemetry" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 10 | ) 11 | 12 | // BeginBlocker of epochs module. 13 | func (k Keeper) BeginBlocker(ctx sdk.Context) { 14 | defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) 15 | k.IterateEpochInfo(ctx, func(index int64, epochInfo types.EpochInfo) (stop bool) { 16 | logger := k.Logger(ctx) 17 | 18 | // If blocktime < initial epoch start time, return 19 | if ctx.BlockTime().Before(epochInfo.StartTime) { 20 | return 21 | } 22 | // if epoch counting hasn't started, signal we need to start. 23 | shouldInitialEpochStart := !epochInfo.EpochCountingStarted 24 | 25 | epochEndTime := epochInfo.CurrentEpochStartTime.Add(epochInfo.Duration) 26 | shouldEpochStart := (ctx.BlockTime().After(epochEndTime)) || shouldInitialEpochStart 27 | 28 | if !shouldEpochStart { 29 | return false 30 | } 31 | epochInfo.CurrentEpochStartHeight = ctx.BlockHeight() 32 | 33 | if shouldInitialEpochStart { 34 | epochInfo.EpochCountingStarted = true 35 | epochInfo.CurrentEpoch = 1 36 | epochInfo.CurrentEpochStartTime = epochInfo.StartTime 37 | logger.Info(fmt.Sprintf("Starting new epoch with identifier %s epoch number %d", epochInfo.Identifier, epochInfo.CurrentEpoch)) 38 | } else { 39 | k.AfterEpochEnd(ctx, epochInfo.Identifier) 40 | epochInfo.CurrentEpoch += 1 41 | epochInfo.CurrentEpochStartTime = epochInfo.CurrentEpochStartTime.Add(epochInfo.Duration) 42 | logger.Info(fmt.Sprintf("Starting epoch with identifier %s epoch number %d", epochInfo.Identifier, epochInfo.CurrentEpoch)) 43 | } 44 | 45 | // emit new epoch start event, set epoch info, and run BeforeEpochStart hook 46 | ctx.EventManager().EmitEvent( 47 | sdk.NewEvent( 48 | types.EventTypeEpochStart, 49 | sdk.NewAttribute(types.AttributeEpochNumber, fmt.Sprintf("%d", epochInfo.CurrentEpoch)), 50 | sdk.NewAttribute(types.AttributeEpochStartTime, fmt.Sprintf("%d", epochInfo.CurrentEpochStartTime.Unix())), 51 | ), 52 | ) 53 | k.setEpochInfo(ctx, epochInfo) 54 | 55 | return false 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /x/feeabs/keeper/config.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 7 | ) 8 | 9 | func (keeper Keeper) HasHostZoneConfig(ctx sdk.Context, ibcDenom string) bool { 10 | store := ctx.KVStore(keeper.storeKey) 11 | key := types.GetKeyHostZoneConfig(ibcDenom) 12 | return store.Has(key) 13 | } 14 | 15 | func (keeper Keeper) GetHostZoneConfig(ctx sdk.Context, ibcDenom string) (chainConfig types.HostChainFeeAbsConfig, err error) { 16 | store := ctx.KVStore(keeper.storeKey) 17 | key := types.GetKeyHostZoneConfig(ibcDenom) 18 | 19 | bz := store.Get(key) 20 | err = keeper.cdc.Unmarshal(bz, &chainConfig) 21 | 22 | if err != nil { 23 | return types.HostChainFeeAbsConfig{}, err 24 | } 25 | 26 | return 27 | } 28 | 29 | func (keeper Keeper) SetHostZoneConfig(ctx sdk.Context, ibcDenom string, chainConfig types.HostChainFeeAbsConfig) error { 30 | store := ctx.KVStore(keeper.storeKey) 31 | key := types.GetKeyHostZoneConfig(ibcDenom) 32 | 33 | bz, err := keeper.cdc.Marshal(&chainConfig) 34 | if err != nil { 35 | return err 36 | } 37 | store.Set(key, bz) 38 | 39 | return nil 40 | } 41 | 42 | func (keeper Keeper) DeleteHostZoneConfig(ctx sdk.Context, ibcDenom string) error { 43 | store := ctx.KVStore(keeper.storeKey) 44 | key := types.GetKeyHostZoneConfig(ibcDenom) 45 | store.Delete(key) 46 | return nil 47 | } 48 | 49 | // use iterator 50 | func (keeper Keeper) GetAllHostZoneConfig(ctx sdk.Context) (allChainConfigs []types.HostChainFeeAbsConfig, err error) { 51 | keeper.IterateHostZone(ctx, func(hostZoneConfig types.HostChainFeeAbsConfig) (stop bool) { 52 | allChainConfigs = append(allChainConfigs, hostZoneConfig) 53 | return false 54 | }) 55 | 56 | return allChainConfigs, nil 57 | } 58 | 59 | func (keeper Keeper) IteratorHostZone(ctx sdk.Context) sdk.Iterator { 60 | store := ctx.KVStore(keeper.storeKey) 61 | return sdk.KVStorePrefixIterator(store, types.KeyHostChainChainConfig) 62 | } 63 | 64 | // IterateHostZone iterates over the hostzone . 65 | func (keeper Keeper) IterateHostZone(ctx sdk.Context, cb func(hostZoneConfig types.HostChainFeeAbsConfig) (stop bool)) { 66 | store := ctx.KVStore(keeper.storeKey) 67 | iterator := sdk.KVStorePrefixIterator(store, types.KeyHostChainChainConfig) 68 | 69 | defer iterator.Close() 70 | for ; iterator.Valid(); iterator.Next() { 71 | var hostZoneConfig types.HostChainFeeAbsConfig 72 | keeper.cdc.MustUnmarshal(iterator.Value(), &hostZoneConfig) 73 | if cb(hostZoneConfig) { 74 | break 75 | } 76 | } 77 | } 78 | 79 | func (keeper Keeper) FrozenHostZoneByIBCDenom(ctx sdk.Context, ibcDenom string) error { 80 | hostChainConfig, err := keeper.GetHostZoneConfig(ctx, ibcDenom) 81 | if err != nil { 82 | // TODO: registry the error here 83 | return sdkerrors.Wrapf(types.ErrHostZoneConfigNotFound, err.Error()) 84 | } 85 | hostChainConfig.Frozen = true 86 | err = keeper.SetHostZoneConfig(ctx, ibcDenom, hostChainConfig) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | return nil 92 | } 93 | 94 | func (keeper Keeper) UnFrozenHostZoneByIBCDenom(ctx sdk.Context, ibcDenom string) error { 95 | hostChainConfig, err := keeper.GetHostZoneConfig(ctx, ibcDenom) 96 | if err != nil { 97 | return sdkerrors.Wrapf(types.ErrHostZoneConfigNotFound, err.Error()) 98 | } 99 | hostChainConfig.Frozen = false 100 | err = keeper.SetHostZoneConfig(ctx, ibcDenom, hostChainConfig) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /x/feeabs/keeper/epoch.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/gogo/protobuf/proto" 8 | 9 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 10 | 11 | sdk "github.com/cosmos/cosmos-sdk/types" 12 | ) 13 | 14 | // HasEpochInfo return true if has epoch info 15 | func (k Keeper) HasEpochInfo(ctx sdk.Context, identifier string) bool { 16 | store := ctx.KVStore(k.storeKey) 17 | return store.Has(append(types.KeyPrefixEpoch, []byte(identifier)...)) 18 | } 19 | 20 | // GetEpochInfo returns epoch info by identifier. 21 | func (k Keeper) GetEpochInfo(ctx sdk.Context, identifier string) types.EpochInfo { 22 | epoch := types.EpochInfo{} 23 | store := ctx.KVStore(k.storeKey) 24 | b := store.Get(append(types.KeyPrefixEpoch, []byte(identifier)...)) 25 | if b == nil { 26 | return epoch 27 | } 28 | err := proto.Unmarshal(b, &epoch) 29 | if err != nil { 30 | panic(err) 31 | } 32 | return epoch 33 | } 34 | 35 | // AddEpochInfo adds a new epoch info. Will return an error if the epoch fails validation, 36 | // or re-uses an existing identifier. 37 | // This method also sets the start time if left unset, and sets the epoch start height. 38 | func (k Keeper) AddEpochInfo(ctx sdk.Context, epoch types.EpochInfo) error { 39 | err := epoch.Validate() 40 | if err != nil { 41 | return err 42 | } 43 | // Check if identifier already exists 44 | if (k.GetEpochInfo(ctx, epoch.Identifier) != types.EpochInfo{}) { 45 | return fmt.Errorf("epoch with identifier %s already exists", epoch.Identifier) 46 | } 47 | 48 | // Initialize empty and default epoch values 49 | if epoch.StartTime.Equal(time.Time{}) { 50 | epoch.StartTime = ctx.BlockTime() 51 | } 52 | epoch.CurrentEpochStartHeight = ctx.BlockHeight() 53 | k.setEpochInfo(ctx, epoch) 54 | return nil 55 | } 56 | 57 | // setEpochInfo set epoch info. 58 | func (k Keeper) setEpochInfo(ctx sdk.Context, epoch types.EpochInfo) { 59 | store := ctx.KVStore(k.storeKey) 60 | value, err := proto.Marshal(&epoch) 61 | if err != nil { 62 | panic(err) 63 | } 64 | store.Set(append(types.KeyPrefixEpoch, []byte(epoch.Identifier)...), value) 65 | } 66 | 67 | // DeleteEpochInfo delete epoch info. 68 | func (k Keeper) DeleteEpochInfo(ctx sdk.Context, identifier string) { 69 | store := ctx.KVStore(k.storeKey) 70 | store.Delete(append(types.KeyPrefixEpoch, []byte(identifier)...)) 71 | } 72 | 73 | // IterateEpochInfo iterate through epochs. 74 | func (k Keeper) IterateEpochInfo(ctx sdk.Context, fn func(index int64, epochInfo types.EpochInfo) (stop bool)) { 75 | store := ctx.KVStore(k.storeKey) 76 | 77 | iterator := sdk.KVStorePrefixIterator(store, types.KeyPrefixEpoch) 78 | defer iterator.Close() 79 | 80 | i := int64(0) 81 | 82 | for ; iterator.Valid(); iterator.Next() { 83 | epoch := types.EpochInfo{} 84 | err := proto.Unmarshal(iterator.Value(), &epoch) 85 | if err != nil { 86 | panic(err) 87 | } 88 | stop := fn(i, epoch) 89 | 90 | if stop { 91 | break 92 | } 93 | i++ 94 | } 95 | } 96 | 97 | // AllEpochInfos iterate through epochs to return all epochs info. 98 | func (k Keeper) AllEpochInfos(ctx sdk.Context) []types.EpochInfo { 99 | epochs := []types.EpochInfo{} 100 | k.IterateEpochInfo(ctx, func(index int64, epochInfo types.EpochInfo) (stop bool) { 101 | epochs = append(epochs, epochInfo) 102 | return false 103 | }) 104 | return epochs 105 | } 106 | 107 | // NumBlocksSinceEpochStart returns the number of blocks since the epoch started. 108 | // if the epoch started on block N, then calling this during block N (after BeforeEpochStart) 109 | // would return 0. 110 | // Calling it any point in block N+1 (assuming the epoch doesn't increment) would return 1. 111 | func (k Keeper) NumBlocksSinceEpochStart(ctx sdk.Context, identifier string) (int64, error) { 112 | epoch := k.GetEpochInfo(ctx, identifier) 113 | if (epoch == types.EpochInfo{}) { 114 | return 0, fmt.Errorf("epoch with identifier %s not found", identifier) 115 | } 116 | return ctx.BlockHeight() - epoch.CurrentEpochStartHeight, nil 117 | } 118 | 119 | func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string) { 120 | switch epochIdentifier { 121 | case types.DefaultQueryEpochIdentifier: 122 | k.Logger(ctx).Info("Epoch interchain query TWAP") 123 | k.executeAllHostChainTWAPQuery(ctx) 124 | case types.DefaultSwapEpochIdentifier: 125 | k.Logger(ctx).Info("Epoch cross chain swap") 126 | k.executeAllHostChainSwap(ctx) 127 | default: 128 | k.Logger(ctx).Error(fmt.Sprintf("Unknown epoch %s", epochIdentifier)) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /x/feeabs/keeper/exchange_rate.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 7 | ) 8 | 9 | // GetTwapRate return Twap Price of ibcDenom 10 | func (k Keeper) GetTwapRate(ctx sdk.Context, ibcDenom string) (sdk.Dec, error) { 11 | store := ctx.KVStore(k.storeKey) 12 | key := types.GetKeyTwapExchangeRate(ibcDenom) 13 | bz := store.Get(key) 14 | if bz == nil { 15 | return sdk.ZeroDec(), sdkerrors.Wrapf(types.ErrInvalidExchangeRate, "Osmosis does not have exchange rate data") 16 | } 17 | 18 | var osmosisExchangeRate sdk.Dec 19 | if err := osmosisExchangeRate.Unmarshal(bz); err != nil { 20 | panic(err) 21 | } 22 | 23 | return osmosisExchangeRate, nil 24 | } 25 | 26 | func (k Keeper) SetTwapRate(ctx sdk.Context, ibcDenom string, osmosisTWAPExchangeRate sdk.Dec) { 27 | store := ctx.KVStore(k.storeKey) 28 | bz, _ := osmosisTWAPExchangeRate.Marshal() 29 | key := types.GetKeyTwapExchangeRate(ibcDenom) 30 | store.Set(key, bz) 31 | } 32 | -------------------------------------------------------------------------------- /x/feeabs/keeper/genesis.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 7 | ) 8 | 9 | // InitGenesis initializes the incentives module's state from a provided genesis state. 10 | func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { 11 | // set Params 12 | k.SetParams(ctx, genState.Params) 13 | // for IBC 14 | k.SetPort(ctx, genState.PortId) 15 | // Only try to bind to port if it is not already bound, since we may already own 16 | // port capability from capability InitGenesis 17 | if !k.IsBound(ctx, genState.PortId) { 18 | // module binds to the port on InitChain 19 | // and claims the returned capability 20 | err := k.BindPort(ctx, genState.PortId) 21 | if err != nil { 22 | panic("could not claim port capability: " + err.Error()) 23 | } 24 | } 25 | 26 | for _, epoch := range genState.Epochs { 27 | err := k.AddEpochInfo(ctx, epoch) 28 | if err != nil { 29 | panic(err) 30 | } 31 | } 32 | 33 | k.ak.GetModuleAccount(ctx, types.ModuleName) 34 | } 35 | 36 | // ExportGenesis returns the x/incentives module's exported genesis. 37 | func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { 38 | params := k.GetParams(ctx) 39 | 40 | return &types.GenesisState{ 41 | Params: params, 42 | Epochs: k.AllEpochInfos(ctx), 43 | PortId: k.GetPort(ctx), 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /x/feeabs/keeper/genesis_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | apphelpers "github.com/notional-labs/fee-abstraction/v2/app/helpers" 10 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 11 | ) 12 | 13 | var now = time.Now().UTC() 14 | 15 | var testGenesis = types.DefaultGenesis() 16 | 17 | func TestInitGenesis(t *testing.T) { 18 | app := apphelpers.Setup(t, false, 1) 19 | ctx := apphelpers.NewContextForApp(*app) 20 | 21 | ctx = ctx.WithBlockHeight(1) 22 | ctx = ctx.WithBlockTime(now) 23 | genesis := testGenesis 24 | 25 | params := app.FeeabsKeeper.GetParams(ctx) 26 | require.Equal(t, params, genesis.Params) 27 | 28 | epochs := app.FeeabsKeeper.AllEpochInfos(ctx) 29 | require.Equal(t, epochs, genesis.Epochs) 30 | 31 | portid := app.FeeabsKeeper.GetPort(ctx) 32 | require.Equal(t, portid, genesis.PortId) 33 | } 34 | 35 | func TestExportGenesis(t *testing.T) { 36 | app := apphelpers.Setup(t, false, 1) 37 | ctx := apphelpers.NewContextForApp(*app) 38 | ctx = ctx.WithBlockHeight(1) 39 | genesis := app.FeeabsKeeper.ExportGenesis(ctx) 40 | require.Len(t, genesis.Epochs, 2) 41 | 42 | expectedEpochs := types.DefaultGenesis().Epochs 43 | require.Equal(t, expectedEpochs, genesis.Epochs) 44 | } 45 | -------------------------------------------------------------------------------- /x/feeabs/keeper/grpc_query.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | var _ types.QueryServer = Querier{} 13 | 14 | // Querier defines a wrapper around the x/feeabstraction keeper providing gRPC method 15 | // handlers. 16 | type Querier struct { 17 | Keeper 18 | } 19 | 20 | func NewQuerier(k Keeper) Querier { 21 | return Querier{Keeper: k} 22 | } 23 | 24 | // OsmosisSpotPrice return spot price of pair Osmo/nativeToken 25 | func (q Querier) OsmosisArithmeticTwap(goCtx context.Context, req *types.QueryOsmosisArithmeticTwapRequest) (*types.QueryOsmosisArithmeticTwapResponse, error) { 26 | if req == nil { 27 | return nil, status.Error(codes.InvalidArgument, "empty request") 28 | } 29 | 30 | ctx := sdk.UnwrapSDKContext(goCtx) 31 | 32 | twapRate, err := q.GetTwapRate(ctx, req.IbcDenom) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return &types.QueryOsmosisArithmeticTwapResponse{ 38 | ArithmeticTwap: twapRate, 39 | }, nil 40 | } 41 | 42 | // FeeabsModuleBalances return total balances of feeabs module 43 | func (q Querier) FeeabsModuleBalances(goCtx context.Context, req *types.QueryFeeabsModuleBalacesRequest) (*types.QueryFeeabsModuleBalacesResponse, error) { 44 | if req == nil { 45 | return nil, status.Error(codes.InvalidArgument, "empty request") 46 | } 47 | 48 | ctx := sdk.UnwrapSDKContext(goCtx) 49 | 50 | moduleAddress := q.GetFeeAbsModuleAddress() 51 | moduleBalances := q.bk.GetAllBalances(ctx, moduleAddress) 52 | 53 | return &types.QueryFeeabsModuleBalacesResponse{ 54 | Balances: moduleBalances, 55 | Address: moduleAddress.String(), 56 | }, nil 57 | } 58 | 59 | func (q Querier) HostChainConfig(goCtx context.Context, req *types.QueryHostChainConfigRequest) (*types.QueryHostChainConfigRespone, error) { 60 | if req == nil { 61 | return nil, status.Error(codes.InvalidArgument, "empty request") 62 | } 63 | 64 | ctx := sdk.UnwrapSDKContext(goCtx) 65 | 66 | hostChainConfig, err := q.GetHostZoneConfig(ctx, req.IbcDenom) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | return &types.QueryHostChainConfigRespone{ 72 | HostChainConfig: hostChainConfig, 73 | }, nil 74 | } 75 | 76 | func (q Querier) AllHostChainConfig(goCtx context.Context, req *types.AllQueryHostChainConfigRequest) (*types.AllQueryHostChainConfigRespone, error) { 77 | if req == nil { 78 | return nil, status.Error(codes.InvalidArgument, "empty request") 79 | } 80 | 81 | ctx := sdk.UnwrapSDKContext(goCtx) 82 | 83 | allHostChainConfig, err := q.GetAllHostZoneConfig(ctx) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | return &types.AllQueryHostChainConfigRespone{ 89 | AllHostChainConfig: allHostChainConfig, 90 | }, nil 91 | } 92 | -------------------------------------------------------------------------------- /x/feeabs/keeper/grpc_query_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 9 | ) 10 | 11 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 12 | 13 | func (suite *KeeperTestSuite) TestOsmosisArithmeticTwap() { 14 | suite.SetupTest() 15 | twapPrice := sdk.NewDec(1) 16 | suite.feeAbsKeeper.SetTwapRate(suite.ctx, "denom", twapPrice) 17 | 18 | for _, tc := range []struct { 19 | desc string 20 | req *types.QueryOsmosisArithmeticTwapRequest 21 | res *types.QueryOsmosisArithmeticTwapResponse 22 | shouldErr bool 23 | }{ 24 | { 25 | desc: "Success", 26 | req: &types.QueryOsmosisArithmeticTwapRequest{ 27 | IbcDenom: "denom", 28 | }, 29 | res: &types.QueryOsmosisArithmeticTwapResponse{ 30 | ArithmeticTwap: twapPrice, 31 | }, 32 | shouldErr: false, 33 | }, 34 | { 35 | desc: "Invalid denom", 36 | req: &types.QueryOsmosisArithmeticTwapRequest{ 37 | IbcDenom: "invalid", 38 | }, 39 | shouldErr: true, 40 | }, 41 | } { 42 | tc := tc 43 | suite.Run(tc.desc, func() { 44 | goCtx := sdk.WrapSDKContext(suite.ctx) 45 | if !tc.shouldErr { 46 | res, err := suite.queryClient.OsmosisArithmeticTwap(goCtx, tc.req) 47 | suite.Require().NoError(err) 48 | suite.Require().Equal(tc.res, res) 49 | } else { 50 | _, err := suite.queryClient.OsmosisArithmeticTwap(goCtx, tc.req) 51 | suite.Require().Error(err) 52 | } 53 | }) 54 | } 55 | } 56 | 57 | func (suite *KeeperTestSuite) TestHostChainConfig() { 58 | suite.SetupTest() 59 | 60 | chainConfig := types.HostChainFeeAbsConfig{ 61 | IbcDenom: randStringRunes(10), 62 | OsmosisPoolTokenDenomIn: randStringRunes(10), 63 | PoolId: randUint64Num(), 64 | } 65 | 66 | err := suite.feeAbsKeeper.SetHostZoneConfig(suite.ctx, chainConfig.IbcDenom, chainConfig) 67 | suite.Require().NoError(err) 68 | 69 | for _, tc := range []struct { 70 | desc string 71 | req *types.QueryHostChainConfigRequest 72 | res *types.QueryHostChainConfigRespone 73 | shouldErr bool 74 | }{ 75 | { 76 | desc: "Success", 77 | req: &types.QueryHostChainConfigRequest{ 78 | IbcDenom: chainConfig.IbcDenom, 79 | }, 80 | res: &types.QueryHostChainConfigRespone{ 81 | HostChainConfig: chainConfig, 82 | }, 83 | shouldErr: false, 84 | }, 85 | { 86 | desc: "Invalid denom", 87 | req: &types.QueryHostChainConfigRequest{ 88 | IbcDenom: "Invalid", 89 | }, 90 | res: &types.QueryHostChainConfigRespone{ 91 | HostChainConfig: chainConfig, 92 | }, 93 | shouldErr: true, 94 | }, 95 | } { 96 | tc := tc 97 | suite.Run(tc.desc, func() { 98 | goCtx := sdk.WrapSDKContext(suite.ctx) 99 | if !tc.shouldErr { 100 | res, err := suite.queryClient.HostChainConfig(goCtx, tc.req) 101 | suite.Require().NoError(err) 102 | suite.Require().Equal(tc.res, res) 103 | } else { 104 | _, err := suite.queryClient.HostChainConfig(goCtx, tc.req) 105 | suite.Require().NoError(err) 106 | } 107 | }) 108 | } 109 | } 110 | 111 | func randStringRunes(n int) string { 112 | rand.Seed(time.Now().UnixNano()) 113 | b := make([]rune, n) 114 | for i := range b { 115 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 116 | } 117 | return string(b) 118 | } 119 | 120 | func randUint64Num() uint64 { 121 | rand.Seed(time.Now().UnixNano()) 122 | return rand.Uint64() 123 | } 124 | -------------------------------------------------------------------------------- /x/feeabs/keeper/host_zone_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/stretchr/testify/require" 8 | 9 | apphelpers "github.com/notional-labs/fee-abstraction/v2/app/helpers" 10 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/keeper" 11 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 12 | ) 13 | 14 | func createNHostZone(t *testing.T, keeper *keeper.Keeper, ctx sdk.Context, n int) []types.HostChainFeeAbsConfig { 15 | var expected []types.HostChainFeeAbsConfig 16 | expectedConfig := types.HostChainFeeAbsConfig{ 17 | IbcDenom: "ibc/123", 18 | OsmosisPoolTokenDenomIn: "ibc/456", 19 | PoolId: 1, 20 | Frozen: false, 21 | } 22 | for i := 0; i < n; i++ { 23 | expected = append(expected, expectedConfig) 24 | err := keeper.SetHostZoneConfig(ctx, expectedConfig.IbcDenom, expectedConfig) 25 | require.NoError(t, err) 26 | } 27 | return expected 28 | } 29 | func TestHostZoneGet(t *testing.T) { 30 | app := apphelpers.Setup(t, false, 1) 31 | ctx := apphelpers.NewContextForApp(*app) 32 | expected := createNHostZone(t, &app.FeeabsKeeper, ctx, 1) 33 | for _, item := range expected { 34 | got, err := app.FeeabsKeeper.GetHostZoneConfig(ctx, item.IbcDenom) 35 | require.NoError(t, err) 36 | require.Equal(t, item, got) 37 | } 38 | } 39 | 40 | func TestHostZoneRemove(t *testing.T) { 41 | app := apphelpers.Setup(t, false, 1) 42 | ctx := apphelpers.NewContextForApp(*app) 43 | expected := createNHostZone(t, &app.FeeabsKeeper, ctx, 1) 44 | for _, item := range expected { 45 | err := app.FeeabsKeeper.DeleteHostZoneConfig(ctx, item.IbcDenom) 46 | require.NoError(t, err) 47 | got, _ := app.FeeabsKeeper.GetHostZoneConfig(ctx, item.IbcDenom) 48 | require.NotEqual(t, item, got) 49 | } 50 | } 51 | 52 | func TestHostZoneGetAll(t *testing.T) { 53 | app := apphelpers.Setup(t, false, 1) 54 | ctx := apphelpers.NewContextForApp(*app) 55 | expected := createNHostZone(t, &app.FeeabsKeeper, ctx, 1) 56 | got, _ := app.FeeabsKeeper.GetAllHostZoneConfig(ctx) 57 | require.ElementsMatch(t, expected, got) 58 | } 59 | -------------------------------------------------------------------------------- /x/feeabs/keeper/keeper.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 9 | capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" 10 | paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" 11 | ibctransferkeeper "github.com/cosmos/ibc-go/v4/modules/apps/transfer/keeper" 12 | "github.com/cosmos/ibc-go/v4/modules/core/exported" 13 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 14 | "github.com/tendermint/tendermint/libs/log" 15 | ) 16 | 17 | type Keeper struct { 18 | cdc codec.BinaryCodec 19 | storeKey sdk.StoreKey 20 | sk types.StakingKeeper 21 | ak types.AccountKeeper 22 | bk types.BankKeeper 23 | transferKeeper ibctransferkeeper.Keeper 24 | paramSpace paramtypes.Subspace 25 | 26 | // ibc keeper 27 | portKeeper types.PortKeeper 28 | channelKeeper types.ChannelKeeper 29 | scopedKeeper types.ScopedKeeper 30 | } 31 | 32 | func NewKeeper( 33 | cdc codec.BinaryCodec, 34 | storeKey sdk.StoreKey, 35 | memKey sdk.StoreKey, 36 | ps paramtypes.Subspace, 37 | sk types.StakingKeeper, 38 | ak types.AccountKeeper, 39 | bk types.BankKeeper, 40 | transferKeeper ibctransferkeeper.Keeper, 41 | 42 | channelKeeper types.ChannelKeeper, 43 | portKeeper types.PortKeeper, 44 | scopedKeeper types.ScopedKeeper, 45 | 46 | ) Keeper { 47 | // set KeyTable if it has not already been set 48 | if !ps.HasKeyTable() { 49 | ps = ps.WithKeyTable(types.ParamKeyTable()) 50 | } 51 | 52 | return Keeper{ 53 | cdc: cdc, 54 | storeKey: storeKey, 55 | paramSpace: ps, 56 | sk: sk, 57 | ak: ak, 58 | bk: bk, 59 | transferKeeper: transferKeeper, 60 | channelKeeper: channelKeeper, 61 | scopedKeeper: scopedKeeper, 62 | portKeeper: portKeeper, 63 | } 64 | } 65 | 66 | func (k Keeper) GetFeeAbsModuleAccount(ctx sdk.Context) authtypes.ModuleAccountI { 67 | return k.ak.GetModuleAccount(ctx, types.ModuleName) 68 | } 69 | 70 | func (k Keeper) GetFeeAbsModuleAddress() sdk.AccAddress { 71 | return k.ak.GetModuleAddress(types.ModuleName) 72 | } 73 | 74 | // need to implement 75 | func (k Keeper) CalculateNativeFromIBCCoins(ctx sdk.Context, ibcCoins sdk.Coins, chainConfig types.HostChainFeeAbsConfig) (coins sdk.Coins, err error) { 76 | err = k.verifyIBCCoins(ctx, ibcCoins) 77 | if err != nil { 78 | return sdk.Coins{}, nil 79 | } 80 | 81 | twapRate, err := k.GetTwapRate(ctx, chainConfig.IbcDenom) 82 | if err != nil { 83 | return sdk.Coins{}, nil 84 | } 85 | 86 | // mul 87 | coin := ibcCoins[0] 88 | nativeFeeAmount := twapRate.MulInt(coin.Amount).RoundInt() 89 | nativeFee := sdk.NewCoin(k.sk.BondDenom(ctx), nativeFeeAmount) 90 | 91 | return sdk.NewCoins(nativeFee), nil 92 | } 93 | 94 | func (k Keeper) SendAbstractionFeeToModuleAccount(ctx sdk.Context, IBCcoins sdk.Coins, nativeCoins sdk.Coins, feePayer sdk.AccAddress) error { 95 | err := k.bk.SendCoinsFromAccountToModule(ctx, feePayer, types.ModuleName, IBCcoins) 96 | if err != nil { 97 | return err 98 | } 99 | return nil 100 | } 101 | 102 | // return err if IBC token isn't in allowed_list 103 | func (k Keeper) verifyIBCCoins(ctx sdk.Context, ibcCoins sdk.Coins) error { 104 | if ibcCoins.Len() != 1 { 105 | return types.ErrInvalidIBCFees 106 | } 107 | 108 | if k.HasHostZoneConfig(ctx, ibcCoins[0].Denom) { 109 | return nil 110 | } 111 | // TODO: we should register error for this 112 | return fmt.Errorf("unallowed %s for tx fee", ibcCoins[0].Denom) 113 | } 114 | 115 | func (k Keeper) Logger(ctx sdk.Context) log.Logger { 116 | return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) 117 | } 118 | 119 | // GetParams gets the fee abstraction module's parameters. 120 | func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { 121 | k.paramSpace.GetParamSet(ctx, ¶ms) 122 | return params 123 | } 124 | 125 | // SetParams sets all of the parameters in the abstraction module. 126 | func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { 127 | k.paramSpace.SetParamSet(ctx, ¶ms) 128 | } 129 | 130 | // OnTimeoutPacket resend packet when timeout 131 | func (k Keeper) OnTimeoutPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI) error { 132 | return k.channelKeeper.SendPacket(ctx, chanCap, packet) 133 | } 134 | 135 | func (k Keeper) GetCapability(ctx sdk.Context, name string) *capabilitytypes.Capability { 136 | cap, ok := k.scopedKeeper.GetCapability(ctx, name) 137 | if !ok { 138 | k.Logger(ctx).Error("Error ErrChannelCapabilityNotFound ") 139 | return nil 140 | } 141 | return cap 142 | } 143 | -------------------------------------------------------------------------------- /x/feeabs/keeper/keeper_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/cosmos/cosmos-sdk/baseapp" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" 10 | "github.com/notional-labs/fee-abstraction/v2/app" 11 | apphelpers "github.com/notional-labs/fee-abstraction/v2/app/helpers" 12 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/keeper" 13 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 14 | "github.com/stretchr/testify/suite" 15 | tmrand "github.com/tendermint/tendermint/libs/rand" 16 | tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 17 | ) 18 | 19 | type KeeperTestSuite struct { 20 | suite.Suite 21 | 22 | ctx sdk.Context 23 | feeAbsApp *app.FeeAbs 24 | feeAbsKeeper keeper.Keeper 25 | govKeeper govkeeper.Keeper 26 | queryClient types.QueryClient 27 | msgServer types.MsgServer 28 | } 29 | 30 | func (suite *KeeperTestSuite) SetupTest() { 31 | suite.feeAbsApp = apphelpers.Setup(suite.T(), false, 1) 32 | suite.ctx = suite.feeAbsApp.BaseApp.NewContext(false, tmproto.Header{ 33 | ChainID: fmt.Sprintf("test-chain-%s", tmrand.Str(4)), 34 | Height: 1, 35 | }) 36 | suite.feeAbsKeeper = suite.feeAbsApp.FeeabsKeeper 37 | suite.govKeeper = suite.feeAbsApp.GovKeeper 38 | 39 | queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.feeAbsApp.InterfaceRegistry()) 40 | types.RegisterQueryServer(queryHelper, keeper.NewQuerier(suite.feeAbsKeeper)) 41 | suite.queryClient = types.NewQueryClient(queryHelper) 42 | 43 | suite.msgServer = keeper.NewMsgServerImpl(suite.feeAbsKeeper) 44 | } 45 | 46 | func TestKeeperTestSuite(t *testing.T) { 47 | suite.Run(t, new(KeeperTestSuite)) 48 | } 49 | -------------------------------------------------------------------------------- /x/feeabs/keeper/module.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | -------------------------------------------------------------------------------- /x/feeabs/keeper/msgserver.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | 8 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 9 | ) 10 | 11 | type msgServer struct { 12 | Keeper 13 | } 14 | 15 | // NewMsgServerImpl returns an implementation of the MsgServer interface 16 | // for the provided Keeper. 17 | func NewMsgServerImpl(keeper Keeper) types.MsgServer { 18 | return &msgServer{ 19 | Keeper: keeper, 20 | } 21 | } 22 | 23 | var _ types.MsgServer = msgServer{} 24 | 25 | // Need to remove this 26 | func (k Keeper) SendQueryIbcDenomTWAP(goCtx context.Context, msg *types.MsgSendQueryIbcDenomTWAP) (*types.MsgSendQueryIbcDenomTWAPResponse, error) { 27 | ctx := sdk.UnwrapSDKContext(goCtx) 28 | _, err := sdk.AccAddressFromBech32(msg.FromAddress) 29 | if err != nil { 30 | return nil, err 31 | } 32 | err = k.handleOsmosisIbcQuery(ctx) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return &types.MsgSendQueryIbcDenomTWAPResponse{}, nil 38 | } 39 | 40 | // Need to remove this 41 | func (k Keeper) SwapCrossChain(goCtx context.Context, msg *types.MsgSwapCrossChain) (*types.MsgSwapCrossChainResponse, error) { 42 | ctx := sdk.UnwrapSDKContext(goCtx) 43 | hostChainConfig, err := k.GetHostZoneConfig(ctx, msg.IbcDenom) 44 | if err != nil { 45 | return &types.MsgSwapCrossChainResponse{}, nil 46 | } 47 | _, err = sdk.AccAddressFromBech32(msg.FromAddress) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | err = k.transferOsmosisCrosschainSwap(ctx, hostChainConfig) 53 | 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | return &types.MsgSwapCrossChainResponse{}, nil 59 | } 60 | 61 | func (k Keeper) FundFeeAbsModuleAccount( 62 | goCtx context.Context, 63 | msg *types.MsgFundFeeAbsModuleAccount, 64 | ) (*types.MsgFundFeeAbsModuleAccountResponse, error) { 65 | // Unwrap context 66 | ctx := sdk.UnwrapSDKContext(goCtx) 67 | // Check sender address 68 | sender, err := sdk.AccAddressFromBech32(msg.FromAddress) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | err = k.bk.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, msg.Amount) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | return &types.MsgFundFeeAbsModuleAccountResponse{}, nil 79 | } 80 | -------------------------------------------------------------------------------- /x/feeabs/keeper/param_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | // func TestGetOsmosisIBCDenomParams(t *testing.T) { 4 | // app := apphelpers.Setup(t, false, 1) 5 | // ctx := apphelpers.NewContextForApp(*app) 6 | 7 | // params := feeabstypes.Params{ 8 | // OsmosisIbcDenom: "ibc/acb", 9 | // } 10 | // app.FeeabsKeeper.SetParams(ctx, params) 11 | 12 | // osmosisIBCDenom := app.FeeabsKeeper.GetOsmosisIBCDenomParams(ctx) 13 | // require.Equal(t, params.OsmosisIbcDenom, osmosisIBCDenom) 14 | // } 15 | -------------------------------------------------------------------------------- /x/feeabs/keeper/proposal.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 6 | ) 7 | 8 | func (k Keeper) AddHostZoneProposal(ctx sdk.Context, p *types.AddHostZoneProposal) error { 9 | config, _ := k.GetHostZoneConfig(ctx, p.HostChainConfig.IbcDenom) 10 | if (config != types.HostChainFeeAbsConfig{}) { 11 | return types.ErrDuplicateHostZoneConfig 12 | } 13 | 14 | err := k.SetHostZoneConfig(ctx, p.HostChainConfig.IbcDenom, *p.HostChainConfig) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | return nil 20 | } 21 | 22 | func (k Keeper) DeleteHostZoneProposal(ctx sdk.Context, p *types.DeleteHostZoneProposal) error { 23 | _, err := k.GetHostZoneConfig(ctx, p.IbcDenom) 24 | if err == nil { 25 | return types.ErrHostZoneConfigNotFound 26 | } 27 | 28 | err = k.DeleteHostZoneConfig(ctx, p.IbcDenom) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func (k Keeper) SetHostZoneProposal(ctx sdk.Context, p *types.SetHostZoneProposal) error { 37 | _, err := k.GetHostZoneConfig(ctx, p.HostChainConfig.IbcDenom) 38 | if err == nil { 39 | return types.ErrHostZoneConfigNotFound 40 | } 41 | 42 | err = k.SetHostZoneConfig(ctx, p.HostChainConfig.IbcDenom, *p.HostChainConfig) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /x/feeabs/keeper/proposal_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | apphelpers "github.com/notional-labs/fee-abstraction/v2/app/helpers" 5 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 6 | ) 7 | 8 | func (suite *KeeperTestSuite) TestAddHostZoneProposal() { 9 | suite.SetupTest() 10 | 11 | for _, tc := range []struct { 12 | desc string 13 | hostChainConfig types.HostChainFeeAbsConfig 14 | shouldErr bool 15 | }{ 16 | { 17 | desc: "Success", 18 | hostChainConfig: types.HostChainFeeAbsConfig{ 19 | IbcDenom: "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", 20 | OsmosisPoolTokenDenomIn: "ibc/9117A26BA81E29FA4F78F57DC2BD90CD3D26848101BA880445F119B22A1E254E", 21 | PoolId: 1, 22 | Frozen: false, 23 | }, 24 | shouldErr: false, 25 | }, 26 | } { 27 | tc := tc 28 | suite.Run(tc.desc, func() { 29 | proposal := apphelpers.AddHostZoneProposalFixture(func(p *types.AddHostZoneProposal) { 30 | p.HostChainConfig = &tc.hostChainConfig 31 | }) 32 | 33 | // store proposal 34 | storedProposal, err := suite.govKeeper.SubmitProposal(suite.ctx, proposal) 35 | suite.Require().NoError(err) 36 | 37 | // execute proposal 38 | handler := suite.govKeeper.Router().GetRoute(storedProposal.ProposalRoute()) 39 | err = handler(suite.ctx, storedProposal.GetContent()) 40 | suite.Require().NoError(err) 41 | 42 | hostChainConfig, err := suite.feeAbsKeeper.GetHostZoneConfig(suite.ctx, tc.hostChainConfig.IbcDenom) 43 | suite.Require().NoError(err) 44 | suite.Require().Equal(tc.hostChainConfig, hostChainConfig) 45 | 46 | // store proposal again and it should error 47 | _, err = suite.govKeeper.SubmitProposal(suite.ctx, proposal) 48 | suite.Require().Error(err) 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /x/feeabs/keeper/swap.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | -------------------------------------------------------------------------------- /x/feeabs/proposal_handler.go: -------------------------------------------------------------------------------- 1 | package feeabs 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/cosmos/cosmos-sdk/client" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 9 | "github.com/cosmos/cosmos-sdk/types/rest" 10 | govclient "github.com/cosmos/cosmos-sdk/x/gov/client" 11 | govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest" 12 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 13 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/keeper" 14 | 15 | cli "github.com/notional-labs/fee-abstraction/v2/x/feeabs/client/cli" 16 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 17 | ) 18 | 19 | var ( 20 | UpdateAddHostZoneClientProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitAddHostZoneProposal, emptyRestHandler) 21 | UpdateDeleteHostZoneClientProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitDeleteHostZoneProposal, emptyRestHandler) 22 | UpdateSetHostZoneClientProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitSetHostZoneProposal, emptyRestHandler) 23 | ) 24 | 25 | // NewHostZoneProposal defines the add host zone proposal handler 26 | func NewHostZoneProposal(k keeper.Keeper) govtypes.Handler { 27 | return func(ctx sdk.Context, content govtypes.Content) error { 28 | switch c := content.(type) { 29 | case *types.AddHostZoneProposal: 30 | return k.AddHostZoneProposal(ctx, c) 31 | case *types.DeleteHostZoneProposal: 32 | return k.DeleteHostZoneProposal(ctx, c) 33 | case *types.SetHostZoneProposal: 34 | return k.SetHostZoneProposal(ctx, c) 35 | default: 36 | return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized ibc proposal content type: %T", c) 37 | } 38 | } 39 | } 40 | 41 | func emptyRestHandler(client.Context) govrest.ProposalRESTHandler { 42 | return govrest.ProposalRESTHandler{ 43 | SubRoute: "unsupported", 44 | Handler: func(w http.ResponseWriter, r *http.Request) { 45 | rest.WriteErrorResponse(w, http.StatusBadRequest, "Legacy REST Routes are not supported") 46 | }, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /x/feeabs/spec/01_concepts.md: -------------------------------------------------------------------------------- 1 | ## Context 2 | 3 | The concrete use cases which motivated this module include: 4 | 5 | - The desire to use IBC token as transaction fees on any chain instead of having to use native token as fee. 6 | - To fully take advantage of the newly represented Osmosis [``swap router``](https://github.com/osmosis-labs/osmosis/tree/main/cosmwasm/contracts) with the [``ibc-hooks``](https://github.com/osmosis-labs/osmosis/tree/main/x/ibc-hooks) module and the [``async-icq``](https://github.com/strangelove-ventures/async-icq) module. 7 | 8 | ## Description 9 | 10 | Fee abstraction modules enable users on any Cosmos chain with IBC connections to pay fee using ibc token. 11 | 12 | Fee-abs implementation: 13 | - Fee-abs module imported to the customer chain. 14 | 15 | The implememtation also uses Osmosis swap router and async-icq module which are already deployed on Osmosis testnet. 16 | -------------------------------------------------------------------------------- /x/feeabs/spec/02_state.md: -------------------------------------------------------------------------------- 1 | # State 2 | 3 | ## OsmosisTwapExchangeRate 4 | 5 | The exchange rate of an ibc denom to Osmosis: `0x01 -> sdk.Dec` 6 | 7 | When we send the `InterchainQueryPacketData` which wrapped `QueryArithmeticTwapToNowRequest` to the `async-icq` module via IBC, the `async-icq` module will send an acknowledgement with price data to the fee abstraction chain. The OsmosisTwapExchangeRate will then be updated based on this value. 8 | This exchange rate is then used to calculate transaction fees in the appropriate IBC denom. By updating the exchange rate based on the most recent price data, we can ensure that transaction fees accurately reflect the current market conditions on Osmosis. 9 | 10 | It's important to note that the exchange rate will fluctuate over time, as it is based on the time-weighted average price (TWAP) of the IBC denom on Osmosis. This means that the exchange rate will reflect the average price of the IBC denom over a certain time period, rather than an instantaneous price. 11 | -------------------------------------------------------------------------------- /x/feeabs/spec/03_epoch.md: -------------------------------------------------------------------------------- 1 | # Epoch 2 | 3 | The fee abstraction levegage the Osmosis `epoch` which is used to schedule the Inter-Blockchain Communication (IBC) send packet requests. These requests are for ``QueryArithmeticTwapToNowRequest` and `SwapIBCToken`. 4 | 5 | The `QueryArithmeticTwapToNowRequest` packet is used to request Time-Weighted Average Price (TWAP) data from Osmosis network. 6 | 7 | The `SwapIBCToken` packet is for a feature of the fee abstraction module which allows for the transfer of IBC fees to the Osmosis cross-swap contract. The module account will then receive the native token associated with the fee.. 8 | 9 | Both these packets are scheduled by the fee abstraction module in accordance with the Osmosis epoch. This allows for efficient and timely transfer of data and tokens. -------------------------------------------------------------------------------- /x/feeabs/spec/04_events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | The `feeabs` module emits the following events: 4 | 5 | ## BeginBlocker 6 | 7 | | Type | Attribute Key | Attribute Value | 8 | | ----------- | ------------- | ----------------------- | 9 | | epoch_start | epoch_number | {currentEpoch} | 10 | | epoch_start | start_time | {currentEpochStartTime} | 11 | 12 | ## IBC 13 | 14 | | Type | Attribute Key | Attribute Value | 15 | | ------------------------------------ | --------------- | --------------- | 16 | | receive_feechain_verification_packet | module | {moduleName} | 17 | | receive_feechain_verification_packet | acknowledgement | {ack} | 18 | | receive_feechain_verification_packet | success | {ack.Result} | 19 | | receive_feechain_verification_packet | ack_error | {ack.Error◊} | 20 | -------------------------------------------------------------------------------- /x/feeabs/spec/05_params.md: -------------------------------------------------------------------------------- 1 | # Parameters 2 | 3 | The `feeabs` module contains the following parameters: 4 | 5 | | Key | Type | Example | 6 | | ------------------------------- | ------------- | --------------------------------------------------------------------- | 7 | | OsmosisExchangeRateUpdatePeriod | time.Duration | 60 | 8 | | AccumulatedOsmosisFeeSwapPeriod | time.Duration | 3600 | 9 | | NativeIbcedInOsmosis | string | ibc/E868642C3BADA0A234BC32D84C16C620E3877F11BFD56487B859E99BA06DB03D | 10 | -------------------------------------------------------------------------------- /x/feeabs/spec/06_gov.md: -------------------------------------------------------------------------------- 1 | 2 | ## HostChainChainConfig 3 | 4 | ```go 5 | type HostChainFeeAbsConfig struct { 6 | // ibc token is allowed to be used as fee token 7 | IbcDenom string `protobuf:"bytes,1,opt,name=ibc_denom,json=ibcDenom,proto3" json:"ibc_denom,omitempty" yaml:"allowed_token"` 8 | // token_in in cross_chain swap contract. 9 | OsmosisPoolTokenDenomIn string `protobuf:"bytes,2,opt,name=osmosis_pool_token_denom_in,json=osmosisPoolTokenDenomIn,proto3" json:"osmosis_pool_token_denom_in,omitempty"` 10 | // TODO: middleware address in hostchain, can we refator this logic ? 11 | MiddlewareAddress string `protobuf:"bytes,3,opt,name=middleware_address,json=middlewareAddress,proto3" json:"middleware_address,omitempty"` 12 | // transfer channel from customer_chain -> host chain 13 | IbcTransferChannel string `protobuf:"bytes,4,opt,name=ibc_transfer_channel,json=ibcTransferChannel,proto3" json:"ibc_transfer_channel,omitempty"` 14 | // transfer channel from host chain -> osmosis 15 | HostZoneIbcTransferChannel string `protobuf:"bytes,5,opt,name=host_zone_ibc_transfer_channel,json=hostZoneIbcTransferChannel,proto3" json:"host_zone_ibc_transfer_channel,omitempty"` 16 | // crosschain-swap contract address 17 | CrosschainSwapAddress string `protobuf:"bytes,6,opt,name=crosschain_swap_address,json=crosschainSwapAddress,proto3" json:"crosschain_swap_address,omitempty"` 18 | // pool id 19 | PoolId uint64 `protobuf:"varint,7,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` 20 | // Active 21 | IsOsmosis bool `protobuf:"varint,8,opt,name=is_osmosis,json=isOsmosis,proto3" json:"is_osmosis,omitempty"` 22 | // Frozen 23 | Frozen bool `protobuf:"varint,9,opt,name=frozen,proto3" json:"frozen,omitempty"` 24 | // Query channel 25 | OsmosisQueryChannel string `protobuf:"bytes,10,opt,name=osmosis_query_channel,json=osmosisQueryChannel,proto3" json:"osmosis_query_channel,omitempty"` 26 | } 27 | ``` 28 | 29 | ## Configuring HostZoneConfig 30 | 31 | In order to use Fee Abstraction, we need to add the HostZoneConfig as specified in the government proposals. 32 | -------------------------------------------------------------------------------- /x/feeabs/spec/07_ibc.md: -------------------------------------------------------------------------------- 1 | # IBCMessages 2 | 3 | ## `SendQueryIbcDenomTWAP` 4 | 5 | A Ibc-token/Native-token TWAP pair is achieved by using the `QueryArithmeticTwapToNowRequest` and `InterchainQueryPacketData`: 6 | 7 | ```go 8 | type QueryArithmeticTwapToNowRequest struct { 9 | PoolId uint64 10 | BaseAsset string 11 | QuoteAsset string 12 | StartTime time.Time 13 | } 14 | ``` 15 | 16 | ```go 17 | type InterchainQueryPacketData struct { 18 | Data []byte 19 | Memo string 20 | } 21 | ``` 22 | 23 | The `QueryArithmeticTwapToNowRequest` will be embedded in the `Data` field of the `InterchainQueryPacketData` 24 | 25 | This message will send a query TWAP to the feeabs-contract on counterparty chain (Osmosis) represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. 26 | 27 | The denomination provided for QueryArithmeticTwapToNowRequest should correspond to the same denomination represented on Osmosis. 28 | 29 | ## `SwapCrossChain` 30 | 31 | Feeabs module exchange Ibc token to native token using the `SwapCrossChain` which is `MsgTransfer` with a specific `Memo`: 32 | 33 | ```go 34 | type MsgTransfer struct { 35 | SourcePort string 36 | SourceChannel string 37 | Token types.Coin 38 | Sender string 39 | Receiver string 40 | TimeoutHeight types1.Height 41 | TimeoutTimestamp uint64 42 | Memo string 43 | } 44 | ``` 45 | 46 | This message is expected to fail if: 47 | 48 | - `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). 49 | - `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). 50 | - `Token` is invalid (denom is invalid or amount is negative) 51 | - `Token.Amount` is not positive. 52 | - `Token.Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](../../../docs/architecture/adr-001-coin-source-tracing.md). 53 | - `Sender` is empty. 54 | - `Receiver` is empty. 55 | - `TimeoutHeight` and `TimeoutTimestamp` are both zero. 56 | 57 | Feeabs module will send an ibc transfer message with a sepecific data in `Memo` field. This `Memo` field data will be used in Ibc transfer middleware on counterparty chain to swap the amount of ibc token to native token on Osmosis. 58 | 59 | There will be 2 separate case that the counterparty chain is Osmosis or not, we will have 2 correspond `Memo`. 60 | 61 | These 2 case are defined in the `IsOsmosis` field in `HostChainFeeAbsConfig` 62 | 63 | ```go 64 | type HostChainFeeAbsConfig struct { 65 | IbcDenom string 66 | OsmosisPoolTokenDenomIn string 67 | MiddlewareAddress string 68 | IbcTransferChannel string 69 | HostZoneIbcTransferChannel string 70 | CrosschainSwapAddress string 71 | PoolId uint64 72 | IsOsmosis bool 73 | Frozen bool 74 | OsmosisQueryChannel string 75 | } 76 | ``` 77 | Note: These 2 Ibc message only open for testing version. In the product version, user can't manual send these 2 message instead, feeabs module will automatic send every epoch to update the TWAP and swap ibc-token to native-token. -------------------------------------------------------------------------------- /x/feeabs/spec/Integration.md: -------------------------------------------------------------------------------- 1 | ## Example integration of the Fee Abstraction module 2 | 3 | ``` 4 | // app.go 5 | import ( 6 | ... 7 | feeabsmodule "github.com/notional-labs/fee-abstraction/v2/x/feeabs" 8 | feeabskeeper "github.com/notional-labs/fee-abstraction/v2/x/feeabs/keeper" 9 | feeabstypes "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 10 | ... 11 | 12 | ) 13 | // Register the AppModule for the fee middleware module 14 | ModuleBasics = module.NewBasicManager( 15 | ... 16 | feeabsmodule.AppModuleBasic{}, 17 | ... 18 | ) 19 | 20 | ... 21 | 22 | // Add module account permissions for the fee abstractions 23 | maccPerms = map[string][]string{ 24 | ... 25 | feeabsmodule.ModuleName: nil, 26 | } 27 | 28 | ... 29 | 30 | // Add fee abstractions Keeper 31 | type App struct { 32 | ... 33 | 34 | FeeabsKeeper feeabskeeper.Keeper 35 | 36 | ... 37 | } 38 | 39 | ... 40 | 41 | // Create store keys 42 | keys := sdk.NewKVStoreKeys( 43 | ... 44 | feeabstypes.StoreKey, 45 | ... 46 | ) 47 | 48 | ... 49 | 50 | app.FeeabsKeeper = feeabskeeper.NewKeeper( 51 | appCodec, 52 | keys[feeabstypes.StoreKey], 53 | keys[feeabstypes.MemStoreKey], 54 | app.GetSubspace(feeabstypes.ModuleName), 55 | app.StakingKeeper, 56 | app.AccountKeeper, 57 | app.BankKeeper, 58 | app.TransferKeeper, 59 | app.IBCKeeper.ChannelKeeper, 60 | &app.IBCKeeper.PortKeeper, 61 | scopedFeeabsKeeper, 62 | ) 63 | 64 | .... 65 | // IBC module to fee abstraction 66 | feeabsIBCModule := feeabsmodule.NewIBCModule(appCodec, app.FeeabsKeeper) 67 | // Create static IBC router, add app routes, then set and seal it 68 | ibcRouter := porttypes.NewRouter() 69 | 70 | ibcRouter. 71 | AddRoute(wasm.ModuleName, wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper)). 72 | AddRoute(ibctransfertypes.ModuleName, transferIBCModule). 73 | AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). 74 | AddRoute(feeabstypes.ModuleName, feeabsIBCModule) 75 | 76 | app.IBCKeeper.SetRouter(ibcRouter) 77 | ... 78 | 79 | // Add fee abstraction to begin blocker logic 80 | 81 | app.moduleManager.SetOrderBeginBlockers( 82 | ... 83 | feeabstypes.ModuleName, 84 | ... 85 | ) 86 | 87 | // Add fee abstraction to end blocker logic 88 | app.moduleManager.SetOrderEndBlockers( 89 | ... 90 | feeabstypes.ModuleName, 91 | ... 92 | ) 93 | 94 | // Add fee abstraction to init genesis logic 95 | app.moduleManager.SetOrderInitGenesis( 96 | ... 97 | feeabstypes.ModuleName, 98 | ... 99 | ) 100 | ``` 101 | 102 | 103 | 104 | ## Modified Fee Deduct Antehandler 105 | 106 | When making a transaction, usually users need to pay fees in the native token, but "fee abstraction" allows them to pay fees in other tokens. 107 | 108 | To allow for this, we use modified versions of `MempoolFeeDecorator` and `DeductFeeDecorate`. In these ante handlers, IBC tokens are swapped to the native token before the next fee handler logic is executed. 109 | 110 | If a blockchain uses the Fee Abstraction module, it is necessary to replace the MempoolFeeDecorator and `DeductFeeDecorate` with the `FeeAbstrationMempoolFeeDecorator` and `FeeAbstractionDeductFeeDecorate`, respectively. 111 | 112 | 113 | Example : 114 | 115 | ``` 116 | anteDecorators := []sdk.AnteDecorator{ 117 | ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first 118 | ante.NewRejectExtensionOptionsDecorator(), 119 | feeabsante.NewFeeAbstrationMempoolFeeDecorator(options.FeeAbskeeper), 120 | ante.NewValidateBasicDecorator(), 121 | ante.NewTxTimeoutHeightDecorator(), 122 | ante.NewValidateMemoDecorator(options.AccountKeeper), 123 | ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), 124 | feeabsante.NewFeeAbstractionDeductFeeDecorate(options.AccountKeeper, options.BankKeeper, options.FeeAbskeeper, options.FeegrantKeeper), 125 | // SetPubKeyDecorator must be called before all signature verification decorators 126 | ante.NewSetPubKeyDecorator(options.AccountKeeper), 127 | ante.NewValidateSigCountDecorator(options.AccountKeeper), 128 | ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), 129 | ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), 130 | ante.NewIncrementSequenceDecorator(options.AccountKeeper), 131 | ibcante.NewAnteDecorator(options.IBCKeeper), 132 | } 133 | 134 | ## Configuring with Fee Abtraction param and HostZoneConfig 135 | In order to use Fee Abstraction, we need to add the HostZoneConfig as specified in the government proposals. 136 | -------------------------------------------------------------------------------- /x/feeabs/spec/README.md: -------------------------------------------------------------------------------- 1 | ## Abstract 2 | 3 | When making a transactions, usually users need to pay fees in native token, but `Feeabs` module enable users on any Cosmos chain which implements this module with IBC connections to pay fee using ibc token. When users use an ibc denom as fees, the ``FeeAbstrationMempoolFeeDecorator`` ante handler will check whether the chain support the transactions to be paid by that ibc denom. It will calculate the amount of ibc tokens equivalent to native token when users make a normal transaction based on Osmosis ``twap`` between ibc denom and native denom. 4 | 5 | After that, the ``FeeAbstractionDeductFeeDecorate`` ante handler swaps ibc token for native token to pay for transaction fees. The accumulated ibc token will be swaped on Osmosis Dex every epoch. 6 | 7 | The `Feeabs` module fetches Osmosis [twap](https://github.com/osmosis-labs/osmosis/tree/main/x/twap) at the begin of every [epoch](01_concepts.md#Epoch) and swap all of ibc tokens left in the module to native token using `swap router` and `ibc hooks` on Osmosis. 8 | 9 | ## Contents 10 | 11 | 1. **[Concepts](01_concepts.md)** 12 | 2. **[State](02_state.md)** 13 | 3. **[Epoch](03_epoch.md)** 14 | 4. **[Events](04_events.md)** 15 | 5. **[Parameters](05_params.md)** 16 | 6. **[Integration](Integration.md)** 17 | 18 | -------------------------------------------------------------------------------- /x/feeabs/spec/kelpr_testnet.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notional-labs/fee-abstraction/2f6e50d468c33dae0f161854561e8719c52e5037/x/feeabs/spec/kelpr_testnet.json -------------------------------------------------------------------------------- /x/feeabs/types/build_memo.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | fmt "fmt" 6 | "time" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | ) 10 | 11 | type OsmosisSpecialMemo struct { 12 | Wasm map[string]interface{} `json:"wasm"` 13 | } 14 | 15 | type OsmosisSwapMsg struct { 16 | OsmosisSwap Swap `json:"osmosis_swap"` 17 | } 18 | type Swap struct { 19 | OutPutDenom string `json:"output_denom"` 20 | Slippage Twap `json:"slippage"` 21 | Receiver string `json:"receiver"` 22 | OnFailedDelivery string `json:"on_failed_delivery"` 23 | } 24 | 25 | type Twap struct { 26 | Twap TwapRouter `json:"twap"` 27 | } 28 | 29 | type TwapRouter struct { 30 | SlippagePercentage string `json:"slippage_percentage"` 31 | WindowSeconds uint64 `json:"window_seconds"` 32 | } 33 | 34 | type PacketMetadata struct { 35 | Forward *ForwardMetadata `json:"forward"` 36 | } 37 | 38 | type ForwardMetadata struct { 39 | Receiver string `json:"receiver,omitempty"` 40 | Port string `json:"port,omitempty"` 41 | Channel string `json:"channel,omitempty"` 42 | Timeout time.Duration `json:"timeout,omitempty"` 43 | Retries *uint8 `json:"retries,omitempty"` 44 | 45 | // Memo for the cross-chain-swap contract 46 | Next string `json:"next,omitempty"` 47 | } 48 | 49 | func NewOsmosisSwapMsg(inputCoin sdk.Coin, outputDenom string, slippagePercentage string, windowSeconds uint64, receiver string) OsmosisSwapMsg { 50 | swap := Swap{ 51 | OutPutDenom: outputDenom, 52 | Slippage: Twap{ 53 | Twap: TwapRouter{SlippagePercentage: slippagePercentage, 54 | WindowSeconds: windowSeconds, 55 | }}, 56 | Receiver: receiver, 57 | } 58 | 59 | return OsmosisSwapMsg{ 60 | OsmosisSwap: swap, 61 | } 62 | } 63 | 64 | // ParseMsgToMemo build a memo from msg, contractAddr, compatible with ValidateAndParseMemo in https://github.com/osmosis-labs/osmosis/blob/nicolas/crosschain-swaps-new/x/ibc-hooks/wasm_hook.go 65 | func ParseMsgToMemo(msg OsmosisSwapMsg, contractAddr string) (string, error) { 66 | // TODO: need to validate the msg && contract address 67 | memo := OsmosisSpecialMemo{ 68 | Wasm: make(map[string]interface{}), 69 | } 70 | 71 | memo.Wasm["contract"] = contractAddr 72 | memo.Wasm["msg"] = msg 73 | 74 | memo_marshalled, err := json.Marshal(&memo) 75 | if err != nil { 76 | return "", err 77 | } 78 | return string(memo_marshalled), nil 79 | } 80 | 81 | // TODO: write test for this 82 | // BuildNextMemo create memo for IBC hook, this execute `CrossChainSwap contract` 83 | func BuildCrossChainSwapMemo(outputDenom string, contractAddress string, receiverAddress string, chainName string) (string, error) { 84 | receiver := fmt.Sprintf("%s/%s", chainName, receiverAddress) 85 | swap := Swap{ 86 | OutPutDenom: outputDenom, 87 | Slippage: Twap{ 88 | Twap: TwapRouter{ 89 | SlippagePercentage: "20", 90 | WindowSeconds: 10, 91 | }, 92 | }, 93 | Receiver: receiver, 94 | OnFailedDelivery: "do_nothing", 95 | } 96 | 97 | msgSwap := OsmosisSwapMsg{ 98 | OsmosisSwap: swap, 99 | } 100 | nextMemo, err := ParseMsgToMemo(msgSwap, contractAddress) 101 | if err != nil { 102 | return "", err 103 | } 104 | 105 | return nextMemo, nil 106 | } 107 | -------------------------------------------------------------------------------- /x/feeabs/types/build_memo_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/notional-labs/fee-abstraction/v2/x/feeabs/types" 9 | ) 10 | 11 | // TODO: need to refactor this test, use driven table 12 | func TestParseMsgToMemo(t *testing.T) { 13 | twapRouter := types.TwapRouter{ 14 | SlippagePercentage: "20", 15 | WindowSeconds: 10, 16 | } 17 | 18 | swap := types.Swap{ 19 | OutPutDenom: "khanhyeuchau", 20 | Slippage: types.Twap{Twap: twapRouter}, 21 | Receiver: "123456", 22 | } 23 | 24 | msgSwap := types.OsmosisSwapMsg{ 25 | OsmosisSwap: swap, 26 | } 27 | 28 | mockAddress := "cosmos123456789" 29 | 30 | //TODO: need to check assert msg 31 | _, err := types.ParseMsgToMemo(msgSwap, mockAddress) 32 | require.NoError(t, err) 33 | } 34 | 35 | // TODO: need to refactor this test, use driven table 36 | func TestParseCrossChainSwapMsgToMemo(t *testing.T) { 37 | outPutDenom := "uosmo" 38 | contractAddress := "osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg" 39 | mockReceiver := "feeabs1efd63aw40lxf3n4mhf7dzhjkr453axurwrhrrw" 40 | chainName := "feeabs" 41 | 42 | execepted_memo_str := `{"wasm":{"contract":"osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg","msg":{"osmosis_swap":{"output_denom":"uosmo","slippage":{"twap":{"slippage_percentage":"20","window_seconds":10}},"receiver":"feeabs/feeabs1efd63aw40lxf3n4mhf7dzhjkr453axurwrhrrw","on_failed_delivery":"do_nothing"}}}}` 43 | //TODO: need to check assert msg 44 | memo_str, err := types.BuildCrossChainSwapMemo(outPutDenom, contractAddress, mockReceiver, chainName) 45 | 46 | require.NoError(t, err) 47 | require.Equal(t, execepted_memo_str, memo_str) 48 | } 49 | -------------------------------------------------------------------------------- /x/feeabs/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | types "github.com/cosmos/cosmos-sdk/codec/types" 6 | cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/types/msgservice" 9 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 10 | ) 11 | 12 | var ( 13 | amino = codec.NewLegacyAmino() 14 | 15 | // AminoCdc references the global x/relationships module codec. Note, the codec should 16 | // ONLY be used in certain instances of tests and for JSON encoding as Amino is 17 | // still used for that purpose. 18 | // 19 | // The actual codec used for serialization should be provided to x/relationships and 20 | // defined at the application level. 21 | AminoCdc = codec.NewAminoCodec(amino) 22 | ) 23 | 24 | func RegisterCodec(cdc *codec.LegacyAmino) { 25 | cdc.RegisterConcrete(&MsgSendQueryIbcDenomTWAP{}, "feeabs/SendQueryIbcDenomTWAP", nil) 26 | cdc.RegisterConcrete(&MsgSwapCrossChain{}, "feeabs/SwapCrossChain", nil) 27 | cdc.RegisterConcrete(&AddHostZoneProposal{}, "feeabs/AddHostZoneProposal", nil) 28 | cdc.RegisterConcrete(&DeleteHostZoneProposal{}, "feeabs/DeleteHostZoneProposal", nil) 29 | cdc.RegisterConcrete(&SetHostZoneProposal{}, "feeabs/SetHostZoneProposal", nil) 30 | // this line is used by starport scaffolding # 2 31 | } 32 | 33 | func RegisterInterfaces(registry types.InterfaceRegistry) { 34 | registry.RegisterImplementations((*sdk.Msg)(nil), 35 | &MsgSendQueryIbcDenomTWAP{}, 36 | &MsgSwapCrossChain{}, 37 | ) 38 | 39 | registry.RegisterImplementations( 40 | (*govtypes.Content)(nil), 41 | &AddHostZoneProposal{}, 42 | &DeleteHostZoneProposal{}, 43 | &SetHostZoneProposal{}, 44 | ) 45 | 46 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 47 | } 48 | 49 | func init() { 50 | RegisterCodec(amino) 51 | cryptocodec.RegisterCrypto(amino) 52 | sdk.RegisterLegacyAminoCodec(amino) 53 | } 54 | -------------------------------------------------------------------------------- /x/feeabs/types/codec_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | -------------------------------------------------------------------------------- /x/feeabs/types/epoch.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | const ( 9 | DefaultSwapPeriod time.Duration = time.Minute * 180 10 | DefaultQueryPeriod time.Duration = time.Minute * 60 11 | DefaultSwapEpochIdentifier string = "swap" 12 | DefaultQueryEpochIdentifier string = "query" 13 | ) 14 | 15 | func KeyPrefix(p string) []byte { 16 | return []byte(p) 17 | } 18 | 19 | // Validate also validates epoch info. 20 | func (epoch EpochInfo) Validate() error { 21 | if epoch.Identifier == "" { 22 | return errors.New("epoch identifier should NOT be empty") 23 | } 24 | if epoch.Duration == 0 { 25 | return errors.New("epoch duration should NOT be 0") 26 | } 27 | if epoch.CurrentEpoch < 0 { 28 | return errors.New("epoch CurrentEpoch must be non-negative") 29 | } 30 | if epoch.CurrentEpochStartHeight < 0 { 31 | return errors.New("epoch CurrentEpoch must be non-negative") 32 | } 33 | return nil 34 | } 35 | 36 | func NewGenesisEpochInfo(identifier string, duration time.Duration) EpochInfo { 37 | return EpochInfo{ 38 | Identifier: identifier, 39 | StartTime: time.Time{}, 40 | Duration: duration, 41 | CurrentEpoch: 0, 42 | CurrentEpochStartHeight: 0, 43 | CurrentEpochStartTime: time.Time{}, 44 | EpochCountingStarted: false, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /x/feeabs/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 5 | ) 6 | 7 | var ( 8 | ErrInvalidExchangeRate = sdkerrors.Register(ModuleName, 1, "invalid exchange rate") 9 | ErrInvalidIBCFees = sdkerrors.Register(ModuleName, 2, "invalid ibc fees") 10 | ErrHostZoneConfigNotFound = sdkerrors.Register(ModuleName, 3, "host chain config not found") 11 | ErrDuplicateHostZoneConfig = sdkerrors.Register(ModuleName, 4, "duplicate config") 12 | ErrNotEnoughFundInModuleAddress = sdkerrors.Register(ModuleName, 6, "not have funding yet") 13 | ) 14 | -------------------------------------------------------------------------------- /x/feeabs/types/events.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | EventTypeTimeout = "timeout" 5 | EventTypePacket = "receive_feechain_verification_packet" 6 | EventTypeEpochEnd = "epoch_end" // TODO: need to clean up (not use) 7 | EventTypeEpochStart = "epoch_start" 8 | AttributeKeyAckSuccess = "success" 9 | AttributeKeyAck = "acknowledgement" 10 | AttributeKeyAckError = "ack_error" 11 | AttributeEpochNumber = "epoch_number" 12 | AttributeEpochStartTime = "start_time" 13 | AttributeKeyFailureType = "failure_type" 14 | AttributeKeyPacket = "packet" 15 | ) 16 | -------------------------------------------------------------------------------- /x/feeabs/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/x/auth/types" 6 | capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" 7 | connectiontypes "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types" 8 | channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" 9 | ibcexported "github.com/cosmos/ibc-go/v4/modules/core/exported" 10 | ) 11 | 12 | // AccountKeeper defines the expected account keeper used for simulations (noalias) 13 | type AccountKeeper interface { 14 | GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI 15 | GetModuleAddress(moduleName string) sdk.AccAddress 16 | GetModuleAccount(ctx sdk.Context, moduleName string) types.ModuleAccountI 17 | } 18 | 19 | // BankKeeper defines the expected interface needed to retrieve account balances. 20 | type BankKeeper interface { 21 | SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 22 | SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 23 | GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin 24 | GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 25 | SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error 26 | } 27 | 28 | type FeegrantKeeper interface { 29 | UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error 30 | } 31 | 32 | // StakingKeeper define the expected interface to retrive staking denom 33 | type StakingKeeper interface { 34 | // BondDenom - Bondable coin denomination 35 | BondDenom(ctx sdk.Context) (res string) 36 | } 37 | 38 | // ClientKeeper defines the expected IBC client keeper. 39 | type ClientKeeper interface { 40 | GetClientConsensusState(ctx sdk.Context, clientID string) (connection ibcexported.ConsensusState, found bool) 41 | } 42 | 43 | // ConnectionKeeper defines the expected IBC connection keeper. 44 | type ConnectionKeeper interface { 45 | GetConnection(ctx sdk.Context, connectionID string) (connection connectiontypes.ConnectionEnd, found bool) 46 | } 47 | 48 | // PortKeeper defines the expected IBC port keeper. 49 | type PortKeeper interface { 50 | BindPort(ctx sdk.Context, portID string) *capabilitytypes.Capability 51 | } 52 | 53 | // ScopedKeeper defines the expected scoped keeper. 54 | type ScopedKeeper interface { 55 | AuthenticateCapability(ctx sdk.Context, capability *capabilitytypes.Capability, name string) bool 56 | ClaimCapability(ctx sdk.Context, capability *capabilitytypes.Capability, name string) error 57 | GetCapability(ctx sdk.Context, name string) (*capabilitytypes.Capability, bool) 58 | } 59 | 60 | // ChannelKeeper defines the expected IBC channel keeper. 61 | type ChannelKeeper interface { 62 | GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) 63 | GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) 64 | SendPacket(ctx sdk.Context, channelCap *capabilitytypes.Capability, packet ibcexported.PacketI) error 65 | ChanCloseInit(ctx sdk.Context, portID, channelID string, chanCap *capabilitytypes.Capability) error 66 | } 67 | -------------------------------------------------------------------------------- /x/feeabs/types/feepool.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: feeabstraction/absfee/v1beta1/feepool.proto 3 | 4 | package types 5 | 6 | import ( 7 | fmt "fmt" 8 | _ "github.com/cosmos/cosmos-sdk/types/query" 9 | _ "github.com/cosmos/gogoproto/gogoproto" 10 | proto "github.com/gogo/protobuf/proto" 11 | _ "google.golang.org/genproto/googleapis/api/annotations" 12 | math "math" 13 | ) 14 | 15 | // Reference imports to suppress errors if they are not otherwise used. 16 | var _ = proto.Marshal 17 | var _ = fmt.Errorf 18 | var _ = math.Inf 19 | 20 | // This is a compile-time assertion to ensure that this generated file 21 | // is compatible with the proto package it is being compiled against. 22 | // A compilation error at this line likely means your copy of the 23 | // proto package needs to be updated. 24 | const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package 25 | 26 | func init() { 27 | proto.RegisterFile("feeabstraction/absfee/v1beta1/feepool.proto", fileDescriptor_aeac12e01fe4cbff) 28 | } 29 | 30 | var fileDescriptor_aeac12e01fe4cbff = []byte{ 31 | // 219 bytes of a gzipped FileDescriptorProto 32 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x8f, 0x31, 0x4e, 0xc5, 0x30, 33 | 0x0c, 0x86, 0xdb, 0x85, 0x81, 0x11, 0x31, 0x3d, 0x41, 0x0e, 0x00, 0x7a, 0xb1, 0x1e, 0x6c, 0x8c, 34 | 0x9c, 0x81, 0x89, 0xcd, 0xa9, 0xdc, 0x10, 0x29, 0x8d, 0x43, 0x9c, 0x57, 0xd1, 0x5b, 0x70, 0x2c, 35 | 0xc6, 0x8e, 0x8c, 0xa8, 0xbd, 0x08, 0x4a, 0x5b, 0x10, 0x2c, 0x6f, 0xb3, 0xe4, 0xef, 0xff, 0x7e, 36 | 0xfb, 0xfc, 0xb6, 0x25, 0x42, 0x23, 0x39, 0x61, 0x93, 0x1d, 0x07, 0x40, 0x23, 0x2d, 0x11, 0xf4, 37 | 0x07, 0x43, 0x19, 0x0f, 0xd0, 0x12, 0x45, 0x66, 0xaf, 0x63, 0xe2, 0xcc, 0x17, 0xd7, 0xff, 0x61, 38 | 0xbd, 0xc2, 0x7a, 0x83, 0x77, 0x97, 0x96, 0x2d, 0x2f, 0x24, 0x94, 0x69, 0x0d, 0xed, 0xae, 0x2c, 39 | 0xb3, 0xf5, 0x04, 0x18, 0x1d, 0x60, 0x08, 0x9c, 0xb1, 0x64, 0x65, 0xdb, 0xde, 0x34, 0x2c, 0x1d, 40 | 0x0b, 0x18, 0x14, 0x82, 0xd7, 0x23, 0xa5, 0xe1, 0xb7, 0x3b, 0xa2, 0x75, 0x61, 0x81, 0x7f, 0xd8, 41 | 0xd3, 0xb7, 0x46, 0x4c, 0xd8, 0x6d, 0xde, 0xc7, 0xa7, 0x8f, 0x49, 0xd5, 0xe3, 0xa4, 0xea, 0xaf, 42 | 0x49, 0xd5, 0xef, 0xb3, 0xaa, 0xc6, 0x59, 0x55, 0x9f, 0xb3, 0xaa, 0x9e, 0x1f, 0xac, 0xcb, 0x2f, 43 | 0x47, 0xa3, 0x1b, 0xee, 0x20, 0x70, 0x11, 0xa1, 0xdf, 0x7b, 0x34, 0x52, 0x9e, 0xdd, 0xff, 0xf5, 44 | 0xf7, 0x77, 0xf0, 0x06, 0x6b, 0x27, 0xe4, 0x21, 0x92, 0x98, 0xb3, 0x45, 0x7e, 0xff, 0x1d, 0x00, 45 | 0x00, 0xff, 0xff, 0x6a, 0x37, 0x68, 0xb3, 0x36, 0x01, 0x00, 0x00, 46 | } 47 | -------------------------------------------------------------------------------- /x/feeabs/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import fmt "fmt" 4 | 5 | // DefaultGenesis returns the incentive module's default genesis state. 6 | func DefaultGenesis() *GenesisState { 7 | return &GenesisState{ 8 | Params: Params{ 9 | OsmosisQueryTwapPath: DefaultOsmosisQueryTwapPath, 10 | NativeIbcedInOsmosis: "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878", 11 | ChainName: DefaultChainName, 12 | }, 13 | Epochs: []EpochInfo{NewGenesisEpochInfo(DefaultQueryEpochIdentifier, DefaultQueryPeriod), NewGenesisEpochInfo(DefaultSwapEpochIdentifier, DefaultSwapPeriod)}, 14 | PortId: IBCPortID, 15 | } 16 | } 17 | 18 | // Validate performs basic genesis state validation, returning an error upon any failure. 19 | func (gs GenesisState) Validate() error { 20 | //Validate params 21 | err := gs.Params.Validate() 22 | if err != nil { 23 | return fmt.Errorf("invalid params %s", err) 24 | } 25 | 26 | // Validate epochs genesis 27 | for _, epoch := range gs.Epochs { 28 | err := epoch.Validate() 29 | if err != nil { 30 | return err 31 | } 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /x/feeabs/types/ibc.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | abci "github.com/tendermint/tendermint/abci/types" 10 | ) 11 | 12 | const ( 13 | // IBCPortID is the default port id that profiles module binds to. 14 | IBCPortID = "feeabs" 15 | ) 16 | 17 | var ModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) 18 | 19 | // IBCPortKey defines the key to store the port ID in store. 20 | var ( 21 | IBCPortKey = []byte{0x01} 22 | FeePoolAddressKey = []byte{0x02} 23 | ) 24 | 25 | // NewQueryArithmeticTwapToNowRequest create new packet for ibc. 26 | func NewQueryArithmeticTwapToNowRequest( 27 | poolID uint64, 28 | baseDenom string, 29 | quoteDenom string, 30 | startTime time.Time, 31 | ) QueryArithmeticTwapToNowRequest { 32 | return QueryArithmeticTwapToNowRequest{ 33 | PoolId: poolID, 34 | BaseAsset: baseDenom, 35 | QuoteAsset: quoteDenom, 36 | StartTime: startTime, 37 | } 38 | } 39 | 40 | func (p QueryArithmeticTwapToNowRequest) GetBytes() []byte { 41 | return ModuleCdc.MustMarshal(&p) 42 | } 43 | 44 | func SerializeCosmosQuery(reqs []abci.RequestQuery) (bz []byte, err error) { 45 | q := &CosmosQuery{ 46 | Requests: reqs, 47 | } 48 | return ModuleCdc.Marshal(q) 49 | } 50 | 51 | func DeserializeCosmosQuery(bz []byte) (reqs []abci.RequestQuery, err error) { 52 | var q CosmosQuery 53 | err = ModuleCdc.Unmarshal(bz, &q) 54 | return q.Requests, err 55 | } 56 | 57 | func SerializeCosmosResponse(resps []abci.ResponseQuery) (bz []byte, err error) { 58 | r := &CosmosResponse{ 59 | Responses: resps, 60 | } 61 | return ModuleCdc.Marshal(r) 62 | } 63 | 64 | func DeserializeCosmosResponse(bz []byte) (resps []abci.ResponseQuery, err error) { 65 | var r CosmosResponse 66 | err = ModuleCdc.Unmarshal(bz, &r) 67 | return r.Responses, err 68 | } 69 | 70 | func NewInterchainQueryRequest(path string, data []byte) InterchainQueryRequest { 71 | return InterchainQueryRequest{ 72 | Data: data, 73 | Path: path, 74 | } 75 | } 76 | 77 | func NewInterchainQueryPacketData(data []byte, memo string) InterchainQueryPacketData { 78 | return InterchainQueryPacketData{ 79 | Data: data, 80 | Memo: memo, 81 | } 82 | } 83 | 84 | // GetBytes returns the JSON marshalled interchain query packet data. 85 | func (p InterchainQueryPacketData) GetBytes() []byte { 86 | return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&p)) 87 | } 88 | -------------------------------------------------------------------------------- /x/feeabs/types/keys.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | // Module name store the name of the module 5 | ModuleName = "feeabs" 6 | 7 | // StoreKey is the string store representation 8 | StoreKey = ModuleName 9 | 10 | // RouterKey is the msg router key for the feeabs module 11 | RouterKey = ModuleName 12 | 13 | // QuerierRoute defines the module's query routing key 14 | QuerierRoute = ModuleName 15 | 16 | // MemStoreKey defines the in-memory store key 17 | MemStoreKey = "mem_feeabs" 18 | 19 | // Contract: Coin denoms cannot contain this character 20 | KeySeparator = "|" 21 | ) 22 | 23 | var ( 24 | OsmosisTwapExchangeRate = []byte{0x01} // Key for the exchange rate of osmosis (to native token) 25 | KeyChannelID = []byte{0x02} // Key for IBC channel to osmosis 26 | KeyHostChainChainConfig = []byte{0x03} // Key for IBC channel to osmosis 27 | KeyPrefixEpoch = []byte{0x04} // KeyPrefixEpoch defines prefix key for storing epochs. 28 | ) 29 | 30 | func GetKeyHostZoneConfig(ibcDenom string) []byte { 31 | return append(KeyHostChainChainConfig, []byte(ibcDenom)...) 32 | } 33 | 34 | func GetKeyTwapExchangeRate(ibcDenom string) []byte { 35 | return append(OsmosisTwapExchangeRate, []byte(ibcDenom)...) 36 | } 37 | -------------------------------------------------------------------------------- /x/feeabs/types/msg.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec/legacy" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 7 | ) 8 | 9 | var _ sdk.Msg = &MsgSendQueryIbcDenomTWAP{} 10 | 11 | // Route Implements Msg. 12 | func (m MsgSendQueryIbcDenomTWAP) Route() string { return sdk.MsgTypeURL(&m) } 13 | 14 | // Type Implements Msg. 15 | func (m MsgSendQueryIbcDenomTWAP) Type() string { return sdk.MsgTypeURL(&m) } 16 | 17 | // GetSigners returns the expected signers for a MsgMintAndAllocateExp . 18 | func (m MsgSendQueryIbcDenomTWAP) GetSigners() []sdk.AccAddress { 19 | daoAccount, err := sdk.AccAddressFromBech32(m.FromAddress) 20 | if err != nil { 21 | panic(err) 22 | } 23 | return []sdk.AccAddress{daoAccount} 24 | } 25 | 26 | // GetSignBytes Implements Msg. 27 | func (m MsgSendQueryIbcDenomTWAP) GetSignBytes() []byte { 28 | return sdk.MustSortJSON(legacy.Cdc.MustMarshalJSON(&m)) 29 | } 30 | 31 | // ValidateBasic does a sanity check on the provided data. 32 | func (m MsgSendQueryIbcDenomTWAP) ValidateBasic() error { 33 | _, err := sdk.AccAddressFromBech32(m.FromAddress) 34 | if err != nil { 35 | return sdkerrors.Wrap(err, "from address must be valid address") 36 | } 37 | return nil 38 | } 39 | 40 | func NewMsgSendQueryIbcDenomTWAP(fromAddr sdk.AccAddress) *MsgSendQueryIbcDenomTWAP { 41 | return &MsgSendQueryIbcDenomTWAP{ 42 | FromAddress: fromAddr.String(), 43 | } 44 | } 45 | 46 | var _ sdk.Msg = &MsgSwapCrossChain{} 47 | 48 | // Route Implements Msg. 49 | func (m MsgSwapCrossChain) Route() string { return sdk.MsgTypeURL(&m) } 50 | 51 | // Type Implements Msg. 52 | func (m MsgSwapCrossChain) Type() string { return sdk.MsgTypeURL(&m) } 53 | 54 | // GetSigners returns the expected signers for a MsgMintAndAllocateExp . 55 | func (m MsgSwapCrossChain) GetSigners() []sdk.AccAddress { 56 | daoAccount, err := sdk.AccAddressFromBech32(m.FromAddress) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return []sdk.AccAddress{daoAccount} 61 | } 62 | 63 | // GetSignBytes Implements Msg. 64 | func (m MsgSwapCrossChain) GetSignBytes() []byte { 65 | return sdk.MustSortJSON(legacy.Cdc.MustMarshalJSON(&m)) 66 | } 67 | 68 | // ValidateBasic does a sanity check on the provided data. 69 | func (m MsgSwapCrossChain) ValidateBasic() error { 70 | _, err := sdk.AccAddressFromBech32(m.FromAddress) 71 | if err != nil { 72 | return sdkerrors.Wrap(err, "from address must be valid address") 73 | } 74 | return nil 75 | } 76 | 77 | func NewMsgSwapCrossChain(fromAddr sdk.AccAddress, ibcDenom string) *MsgSwapCrossChain { 78 | return &MsgSwapCrossChain{ 79 | FromAddress: fromAddr.String(), 80 | IbcDenom: ibcDenom, 81 | } 82 | } 83 | 84 | var _ sdk.Msg = &MsgFundFeeAbsModuleAccount{} 85 | 86 | // Route Implements Msg. 87 | func (m MsgFundFeeAbsModuleAccount) Route() string { return sdk.MsgTypeURL(&m) } 88 | 89 | // Type Implements Msg. 90 | func (m MsgFundFeeAbsModuleAccount) Type() string { return sdk.MsgTypeURL(&m) } 91 | 92 | // GetSigners returns the expected signers for a MsgMintAndAllocateExp . 93 | func (m MsgFundFeeAbsModuleAccount) GetSigners() []sdk.AccAddress { 94 | daoAccount, err := sdk.AccAddressFromBech32(m.FromAddress) 95 | if err != nil { 96 | panic(err) 97 | } 98 | return []sdk.AccAddress{daoAccount} 99 | } 100 | 101 | // GetSignBytes Implements Msg. 102 | func (m MsgFundFeeAbsModuleAccount) GetSignBytes() []byte { 103 | return sdk.MustSortJSON(legacy.Cdc.MustMarshalJSON(&m)) 104 | } 105 | 106 | // ValidateBasic does a sanity check on the provided data. 107 | func (m MsgFundFeeAbsModuleAccount) ValidateBasic() error { 108 | _, err := sdk.AccAddressFromBech32(m.FromAddress) 109 | if err != nil { 110 | return sdkerrors.Wrap(err, "from address must be valid address") 111 | } 112 | return nil 113 | } 114 | 115 | func NewMsgFundFeeAbsModuleAccount(fromAddr sdk.AccAddress, amount sdk.Coins) *MsgFundFeeAbsModuleAccount { 116 | return &MsgFundFeeAbsModuleAccount{ 117 | FromAddress: fromAddr.String(), 118 | Amount: amount, 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /x/feeabs/types/params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" 7 | ) 8 | 9 | // Feeabs params default values . 10 | const ( 11 | DefaultOsmosisQueryTwapPath = "/osmosis.twap.v1beta1.Query/ArithmeticTwapToNow" 12 | DefaultChainName = "feeappd-t1" 13 | DefaultContractAddress = "" 14 | ) 15 | 16 | // Parameter keys store keys. 17 | var ( 18 | KeyOsmosisQueryTwapPath = []byte("osmosisquerytwappath") 19 | KeyNativeIbcedInOsmosis = []byte("nativeibcedinosmosis") 20 | KeyChainName = []byte("chainname") 21 | KeyIbcTransferChannel = []byte("ibctransferchannel") 22 | KeyIbcQueryIcqChannel = []byte("ibcqueryicqchannel") 23 | KeyOsmosisCrosschainSwapAddress = []byte("osmosiscrosschainswapaddress") 24 | 25 | _ paramtypes.ParamSet = &Params{} 26 | ) 27 | 28 | // ParamTable for lockup module. 29 | func ParamKeyTable() paramtypes.KeyTable { 30 | return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) 31 | } 32 | 33 | // Implements params.ParamSet. 34 | func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { 35 | return paramtypes.ParamSetPairs{ 36 | paramtypes.NewParamSetPair(KeyOsmosisQueryTwapPath, &p.OsmosisQueryTwapPath, validateString), 37 | paramtypes.NewParamSetPair(KeyNativeIbcedInOsmosis, &p.NativeIbcedInOsmosis, validateString), 38 | paramtypes.NewParamSetPair(KeyChainName, &p.ChainName, validateString), 39 | paramtypes.NewParamSetPair(KeyIbcTransferChannel, &p.IbcTransferChannel, validateString), 40 | paramtypes.NewParamSetPair(KeyIbcQueryIcqChannel, &p.IbcQueryIcqChannel, validateString), 41 | paramtypes.NewParamSetPair(KeyOsmosisCrosschainSwapAddress, &p.OsmosisCrosschainSwapAddress, validateString), 42 | } 43 | } 44 | 45 | // Validate also validates params info. 46 | func (p Params) Validate() error { 47 | 48 | if err := validateString(p.OsmosisQueryTwapPath); err != nil { 49 | return err 50 | } 51 | if err := validateString(p.NativeIbcedInOsmosis); err != nil { 52 | return err 53 | } 54 | 55 | return nil 56 | } 57 | 58 | func validateString(i interface{}) error { 59 | _, ok := i.(string) 60 | if !ok { 61 | return fmt.Errorf("invalid parameter type string: %T", i) 62 | } 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /x/feeabs/types/pool.go: -------------------------------------------------------------------------------- 1 | package types 2 | -------------------------------------------------------------------------------- /x/feeabs/types/proposal.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 5 | ) 6 | 7 | var ( 8 | _ govtypes.Content = &AddHostZoneProposal{} 9 | _ govtypes.Content = &DeleteHostZoneProposal{} 10 | _ govtypes.Content = &SetHostZoneProposal{} 11 | ) 12 | 13 | const ( 14 | // ProposalTypeAddHostZone defines the type for a AddHostZoneProposal 15 | ProposalTypeAddHostZone = "AddHostZone" 16 | ProposalTypeDeleteHostZone = "DeleteHostZone" 17 | ProposalTypeSetHostZone = "SetHostZone" 18 | ) 19 | 20 | func init() { 21 | govtypes.RegisterProposalType(ProposalTypeAddHostZone) 22 | govtypes.RegisterProposalType(ProposalTypeDeleteHostZone) 23 | govtypes.RegisterProposalType(ProposalTypeSetHostZone) 24 | } 25 | 26 | // NewClientUpdateProposal creates a new client update proposal. 27 | func NewAddHostZoneProposal(title, description string, config HostChainFeeAbsConfig) govtypes.Content { 28 | return &AddHostZoneProposal{ 29 | Title: title, 30 | Description: description, 31 | HostChainConfig: &config, 32 | } 33 | } 34 | 35 | func NewDeleteHostZoneProposal(title, description, ibc_denom string) govtypes.Content { 36 | return &DeleteHostZoneProposal{ 37 | Title: title, 38 | Description: description, 39 | IbcDenom: ibc_denom, 40 | } 41 | } 42 | 43 | func NewSetHostZoneProposal(title, description string, config HostChainFeeAbsConfig) govtypes.Content { 44 | return &SetHostZoneProposal{ 45 | Title: title, 46 | Description: description, 47 | HostChainConfig: &config, 48 | } 49 | } 50 | 51 | // GetTitle returns the title of a client update proposal. 52 | func (ahzp *AddHostZoneProposal) GetTitle() string { return ahzp.Title } 53 | 54 | // GetDescription returns the description of a client update proposal. 55 | func (ahzp *AddHostZoneProposal) GetDescription() string { return ahzp.Description } 56 | 57 | // ProposalRoute returns the routing key of a client update proposal. 58 | func (ahzp *AddHostZoneProposal) ProposalRoute() string { return RouterKey } 59 | 60 | // ProposalType returns the type of a client update proposal. 61 | func (ahzp *AddHostZoneProposal) ProposalType() string { return ProposalTypeAddHostZone } 62 | 63 | // ValidateBasic runs basic stateless validity checks 64 | func (ahzp *AddHostZoneProposal) ValidateBasic() error { 65 | err := govtypes.ValidateAbstract(ahzp) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | // TODO: add validate here 71 | 72 | return nil 73 | } 74 | 75 | // GetTitle returns the title of a client update proposal. 76 | func (dhzp *DeleteHostZoneProposal) GetTitle() string { return dhzp.Title } 77 | 78 | // GetDescription returns the description of a client update proposal. 79 | func (dhzp *DeleteHostZoneProposal) GetDescription() string { return dhzp.Description } 80 | 81 | // ProposalRoute returns the routing key of a client update proposal. 82 | func (dhzp *DeleteHostZoneProposal) ProposalRoute() string { return RouterKey } 83 | 84 | // ProposalType returns the type of a client update proposal. 85 | func (dhzp *DeleteHostZoneProposal) ProposalType() string { return ProposalTypeDeleteHostZone } 86 | 87 | // ValidateBasic runs basic stateless validity checks 88 | func (dhzp *DeleteHostZoneProposal) ValidateBasic() error { 89 | err := govtypes.ValidateAbstract(dhzp) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | // TODO: add validate here 95 | 96 | return nil 97 | } 98 | 99 | // GetTitle returns the title of a client update proposal. 100 | func (shzp *SetHostZoneProposal) GetTitle() string { return shzp.Title } 101 | 102 | // GetDescription returns the description of a client update proposal. 103 | func (shzp *SetHostZoneProposal) GetDescription() string { return shzp.Description } 104 | 105 | // ProposalRoute returns the routing key of a client update proposal. 106 | func (shzp *SetHostZoneProposal) ProposalRoute() string { return RouterKey } 107 | 108 | // ProposalType returns the type of a client update proposal. 109 | func (shzp *SetHostZoneProposal) ProposalType() string { return ProposalTypeSetHostZone } 110 | 111 | // ValidateBasic runs basic stateless validity checks 112 | func (shzp *SetHostZoneProposal) ValidateBasic() error { 113 | err := govtypes.ValidateAbstract(shzp) 114 | if err != nil { 115 | return err 116 | } 117 | 118 | // TODO: add validate here 119 | 120 | return nil 121 | } 122 | --------------------------------------------------------------------------------