Compare commits

...

No commits in common. "development" and "david/swap-zippo" have entirely different histories.

1663 changed files with 197426 additions and 163825 deletions

36
.dockerignore Normal file
View File

@ -0,0 +1,36 @@
**/.git
# dependencies
**/node_modules/
# misc
**/.DS_Store
**/*.pem
**/.idea
# debug
**/yarn-debug.log*
**/yarn-error.log*
# env files
**/.env
**/.env.local
**/.env.development.local
**/.env.test.local
**/.env.production.local
# logs
**/*.log
# turbo
**/.turbo/
# other package managers
**/package-lock.json
**/pnpm-lock.yaml
# build
**/__build__/
**/dist/
**/out/
**/__generated__/

6
.gitattributes vendored
View File

@ -1,4 +1,4 @@
*.sol linguist-language=Solidity
./yarn.lock -diff linguist-generated=true
# Automatically collapse generated files in GitHub.
*.svg linguist-generated=true
# https://www.aleksandrhovhannisyan.com/blog/crlf-vs-lf-normalizing-line-endings-in-git/#a-simple-gitattributes-config
* text=auto

18
.github/CODEOWNERS vendored
View File

@ -1,18 +0,0 @@
# See https://help.github.com/articles/about-codeowners/
# for more info about CODEOWNERS file
# It uses the same pattern rule for gitignore file
# https://git-scm.com/docs/gitignore#_pattern_format
# Dev tools & setup
.github/ @dekz
packages/contract-addresses/ @dekz @dextracker @kyu-c
packages/contract-artifacts/ @dekz
packages/protocol-utils/ @dekz
# Protocol/smart contracts
contracts/ @dekz @dextracker

View File

@ -1,46 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@0xproject.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -1,91 +0,0 @@
## 0x Contribution Guide
We welcome contributions from anyone on the internet and are grateful for even the smallest contributions. This document will help get you setup to start contributing back to 0x.
### Getting started
1. Fork `0xproject/0x-tools`
2. Clone your fork
3. Follow the [installation & build steps](https://github.com/0xProject/0x-tools#install-dependencies) in the repo's top-level README.
4. Setup the recommended [Development Tooling](#development-tooling).
5. Open a [draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/) against the `development` branch and describe the change you are intending to undertake in the PR description. (see [our branch naming conventions](#branch-structure))
Before making the PR "Ready for review", make sure:
- It passes our linter checks (`yarn lint`)
- It is properly formatted with Prettier (`yarn prettier`)
- It passes our continuous integration tests (See: [Enabling code coverage checks on your fork](#fix-submit-coverage-ci-failure) for instructions on getting the `submit-coverage` test to pass on forks)
- You've created/updated the corresponding [CHANGELOG](#CHANGELOGs) entries.
- Your changes have sufficient test coverage (e.g regression tests have been added for bug fixes)
### Branch structure
We have two main branches:
- `master` represents the most recently released (published on npm) version of the codebase.
- `development` represents the current development state of the codebase.
ALL PRs should be opened against `development`.
Branch names should be prefixed with `fix`, `feature` or `refactor`.
- e.g `fix/missing-import`
- If the PR only edits a single package, add it's name too
- e.g `fix/subproviders/missing-import`
### CHANGELOGs
At 0x we use [Semantic Versioning](http://semver.org/) for all our published packages. If a change you make corresponds to a semver bump, you must modify the package's `CHANGELOG.json` file accordingly.
Each CHANGELOG entry that corresponds to a published package will have a `timestamp`. If no entry exists without a `timestamp`, you must first create a new one:
```
{
"version": "1.0.1", <- The updated package version
"changes": [
{
"note": "", <- Describe your change
"PR": 100 <- Your PR number
}
]
},
```
If an entry without a `timestamp` already exists, this means other changes have been introduced by other collaborators since the last publish. Add your changes to the list of notes and adjust the version if your PR introduces a greater semver change (i.e current changes required a patch bump, but your changes require a major version bump).
### Development Tooling
We strongly recommend you use the [VSCode](https://code.visualstudio.com/) text editor since most of our code is written in TypeScript and it offers amazing support for the language.
#### Linter
We use [ESLint](https://eslint.org/docs/latest/) to keep our code-style consistent.
Use `yarn lint` to lint the entire monorepo, and `PKG={PACKAGE_NAME} yarn lint` to lint a specific package.
Integrate it into your text editor:
- VSCode: [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- Atom: [ESLint](https://atom.io/packages/eslint)
#### Auto-formatter
We use [Prettier](https://prettier.io/) to auto-format our code. Be sure to either add a [text editor integration](https://prettier.io/docs/en/editors.html) or a [pre-commit hook](https://prettier.io/docs/en/precommit.html) to properly format your code changes.
If using the Atom text editor, we recommend you install the following packages:
- VSCode: [prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
- Atom: [prettier-atom](https://atom.io/packages/prettier-atom)
## Unenforced coding conventions
A few of our coding conventions are not yet enforced by the linter/auto-formatter. Be careful to follow these conventions in your PR's.
1. Unused anonymous function parameters should be named with an underscore + number (e.g \_1, \_2, etc...)
1. There should be a new-line between methods in a class and between test cases.
1. If a string literal has the same value in two or more places, it should be a single constant referenced in both places.
1. Do not import from a project's `index.ts` (e.g import { Token } from '../src';). Always import from the source file itself.
1. Generic error variables should be named `err` instead of `e` or `error`.
1. If you _must_ cast a variable to any - try to type it back as fast as possible. (e.g., `const cw = ((zeroEx as any)._contractWrappers as ContractWrappers);`). This ensures subsequent code is type-safe.
1. Our enum conventions coincide with the recommended TypeScript conventions, using capitalized keys, and all-caps snake-case values. Eg `GetStats = 'GET_STATS'`
1. All public, exported methods/functions/classes must have associated Javadoc-style comments.

69
.github/Dockerfile-node vendored Normal file
View File

@ -0,0 +1,69 @@
# Inspired by https://turbo.build/repo/docs/handbook/deploying-with-docker#the-problem
FROM node:18-alpine AS builder
ARG WORKSPACE_NAME
ENV WORKSPACE_NAME=$WORKSPACE_NAME
RUN apk add --update --upgrade --no-cache \
libc6-compat
WORKDIR /app
RUN yarn global add turbo
COPY . .
RUN turbo prune --scope=${WORKSPACE_NAME} --docker && \
yarn cache clean
# Add lockfile and package.json's of isolated subworkspace
FROM node:18-alpine AS installer
RUN apk add --uppgrade --update --no-cache \
libc6-compat \
git
WORKDIR /app
# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/yarn.lock ./yarn.lock
RUN yarn install --frozen-lockfile
# Build the project
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
ARG TURBO_TEAM
ENV TURBO_TEAM=$TURBO_TEAM
ARG TURBO_TOKEN
ENV TURBO_TOKEN=$TURBO_TOKEN
ARG WORKSPACE_NAME
ENV WORKSPACE_NAME=$WORKSPACE_NAME
# https://turbo.build/repo/docs/core-concepts/monorepos/filtering#include-dependencies-of-matched-workspaces
RUN yarn turbo run build --filter=${WORKSPACE_NAME}... && \
yarn cache clean
FROM node:18-alpine AS runner
RUN apk add --no-cache --update --upgrade \
ca-certificates \
libc6-compat && \
ln -s /lib/libc.musl-x86_64.so.1 /lib/ld-linux-x86-64.so.2
WORKDIR /app
# Don't run production as root
USER node:node
COPY --from=installer /app/ .
# TODO (rhinodavid): We could add enhancements here to only
# copy the build files
CMD echo "use docker cli to choose start command"

View File

@ -1,25 +0,0 @@
## Description
<!--- Describe your changes in detail -->
## Testing instructions
<!--- Please describe how reviewers can test your changes -->
## Types of changes
<!--- What types of changes does your code introduce? Uncomment all the bullets that apply: -->
<!-- * Bug fix (non-breaking change which fixes an issue) -->
<!-- * New feature (non-breaking change which adds functionality) -->
<!-- * Breaking change (fix or feature that would cause existing functionality to change) -->
## Checklist:
<!--- The following points should be used to indicate the progress of your PR. Put an `x` in all the boxes that apply right now, and come back over time and check them off as you make progress. If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] Add tests to cover changes as needed.
- [ ] Update documentation as needed.
- [ ] Add new entries to the relevant CHANGELOG.jsons.

View File

@ -1,2 +0,0 @@
documentation: ['docs']
liquidity integrations: ['contracts/zero-ex/contracts/src/transformers']

View File

@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
target-branch: "development"

13
.github/scripts/check-for-image.sh vendored Executable file
View File

@ -0,0 +1,13 @@
# Checks if an image with the tag $ECR_IMAGE_TAG exists in ECR
# and sets the output variable image-exists to true or false.
# https://stackoverflow.com/a/22010339/5840249
cmd="aws ecr describe-images --repository-name apps --image-ids imageTag=${ECR_IMAGE_TAG}"
$cmd
status=$?
if [ $status -eq 0 ]; then
echo "image-exists=1" >>$GITHUB_OUTPUT
else
echo "image-exists=0" >>$GITHUB_OUTPUT
fi

View File

@ -1,163 +1,222 @@
name: Continuous Integration
name: CI
on:
push:
branches:
- main
- development
pull_request:
pull_request:
push:
branches:
- main
jobs:
build:
name: Build and test
runs-on: ubuntu-latest
env:
ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }}
AVALANCHE_RPC_URL: ${{ secrets.AVALANCHE_RPC_URL }}
BSC_RPC_URL: ${{ secrets.BSC_RPC_URL }}
FANTOM_RPC_URL: ${{ secrets.FANTOM_RPC_URL }}
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
OPTIMISM_RPC_URL: ${{ secrets.OPTIMISM_RPC_URL }}
POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
build_and_test:
name: Build, Lint, and Test
timeout-minutes: 20
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
strategy:
matrix:
node-version: [18.x]
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '16'
steps:
- name: Check out code
uses: actions/checkout@v3
with:
submodules: recursive
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Add foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- name: Add foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "yarn"
- name: Build solution
run: yarn build
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-build-${{ matrix.node-version }}-${{ hashFiles('yarn.lock') }}
- name: Lint typescript
run: yarn lint:ts
- name: Install dependencies
# See https://github.com/yarnpkg/yarn/issues/5840
run: |
yarn install
git diff yarn.lock
if ! git diff --exit-code yarn.lock; then
echo "Changes were detected in yarn.lock file after running 'yarn install', which is not expected. Please run 'yarn install' locally and commit the changes.";
exit 1;
fi
- name: Lint solidity
run: yarn lint:contracts
- name: Build
run: yarn build
- name: Run prettier
run: yarn prettier:ci
- name: Build (no-diff)
run: |
yarn build:no-diff
git diff --exit-code
- name: Check dependent packages have consistent versions
run: yarn deps_versions:ci
- name: Lint
run: yarn lint:ci
- name: Check diff in docs
run: yarn diff_md_docs:ci
- name: Test
run: yarn test:ci
- name: Check for broken links in markdown files
run: yarn test:links
- name: Get Turbo info
id: turbo-info
uses: ./packages/turbo-dry-run-action
- name: Test doc generation
run: yarn test:generate_docs:ci
- name: Comment commit with workspace hashes
uses: ./packages/workspace-commit-comment-action
with:
dry-run-result: ${{ steps.turbo-info.outputs.dry-run-output }}
require-consistent-names: "true"
- name: Test @0x/contracts-*
run: |
yarn wsrun \
-p @0x/contracts-multisig \
-p @0x/contracts-utils \
-p @0x/contracts-exchange-libs \
-p @0x/contracts-erc721 \
-p @0x/contracts-erc1155 \
-p @0x/contracts-asset-proxy \
-p @0x/contracts-broker \
-p @0x/contracts-zero-ex \
-m --serial -c test:ci
- name: Get node app hashes
id: app-hashes
uses: ./packages/workspace-hash-action
with:
dry-run-result: ${{ steps.turbo-info.outputs.dry-run-output }}
dir: apps-node
outputs:
matrix: ${{ steps.app-hashes.outputs.workspace-hashes }}
turbo_info: ${{ steps.turbo-info.outputs.dry-run-output }}
build_images:
env:
REPOSITORY_URI: 883408475785.dkr.ecr.us-east-1.amazonaws.com/apps
permissions:
id-token: write
contents: read
pull-requests: write
# TODO (rhinodavid): Consider separating this into a reusable workflow
# https://docs.github.com/en/actions/using-workflows/reusing-workflows#creating-a-reusable-workflow
name: Build image ${{ matrix.package-id }}
needs: build_and_test
if: ${{ needs.build_and_test.outputs.matrix != '[]' && needs.build_and_test.outputs.matrix != '' }}
- name: Test local @0x/contracts-*
run: |
yarn wsrun \
-p @0x/contracts-test-utils \
-p @0x/contract-addresses \
-p @0x/contract-artifacts \
-p @0x/contract-wrappers-test \
-p @0x/order-utils \
-m --serial -c test:ci
runs-on: ubuntu-latest
strategy:
matrix:
package-id: ${{ fromJson(needs.build_and_test.outputs.matrix) }}
# NOTE: disabled as ZRXToken.sol did not compile with the latest forge.
# TODO: re-enable once the issue is resolved.
- name: Run Forge build for erc20
working-directory: contracts/erc20
run: |
forge --version
forge build --sizes --skip ZRXToken
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Get workspace name and hash
id: get-workspace-name-and-hash
run: |
echo "WORKSPACE_NAME=$(echo '${{ matrix.package-id }}' | cut -d '@' -f 1)" >> $GITHUB_ENV
echo "WORKSPACE_HASH=$(echo '${{ matrix.package-id }}' | cut -d '@' -f 2)" >> $GITHUB_ENV
- name: Set ECR image tag
run: echo "ECR_IMAGE_TAG=${{ env.WORKSPACE_NAME }}__${{ env.WORKSPACE_HASH }}" >> $GITHUB_ENV
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: arn:aws:iam::883408475785:role/github-actions
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# - name: Run Forge tests for erc20
# working-directory: contracts/erc20
# run: |
# forge test -vvv --gas-report
- name: Check for existing images
id: check-for-image
run: ./.github/scripts/check-for-image.sh
shell: bash
# - name: Run Forge coverage for erc20
# working-directory: contracts/erc20
# run: |
# forge coverage --report summary --report lcov
- if: ${{ steps.check-for-image.outputs.image-exists == true }}
name: Skip building image
run: |
echo "### :rocket: An image exists for ${{ env.WORKSPACE_NAME }} @ ${{ env.WORKSPACE_HASH }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Access this image at" >> $GITHUB_STEP_SUMMARY
echo "\`${{ env.REPOSITORY_URI }}:${{ env.WORKSPACE_NAME }}__${{ env.WORKSPACE_HASH }}\`" >> $GITHUB_STEP_SUMMARY
# - name: Upload the coverage report to Coveralls
# uses: coverallsapp/github-action@master
# with:
# github-token: ${{ secrets.GITHUB_TOKEN }}
# base-path: ./contracts/erc20/
# path-to-lcov: ./contracts/erc20/lcov.info
# If the event is a pull request, `github.sha` is the merge commit.
# We want to use the head commit instead.
# See https://github.com/orgs/community/discussions/25191#discussioncomment-3246770
- name: Set commit sha for pull request
if: ${{ github.event_name == 'pull_request' }}
run: echo "COMMIT_SHA=${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV
- name: Run Forge build for zero-ex
working-directory: contracts/zero-ex
run: |
forge --version
forge build --sizes
- name: Set commit sha for push
if: ${{ github.event_name != 'pull_request' }}
run: echo "COMMIT_SHA=${{ github.sha }}" >> $GITHUB_ENV
- name: Run Forge tests for zero-ex
working-directory: contracts/zero-ex
run: |
forge test -vvv --gas-report
- if: ${{ steps.check-for-image.outputs.image-exists == false }}
name: Build and push image
uses: docker/build-push-action@v3
with:
build-args: |
WORKSPACE_NAME=${{ env.WORKSPACE_NAME }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
file: ./.github/Dockerfile-node
push: true
tags: |
${{ env.REPOSITORY_URI }}:${{ env.WORKSPACE_NAME }}_${{ env.COMMIT_SHA }}
${{ env.REPOSITORY_URI }}:${{ env.WORKSPACE_NAME }}__${{ env.WORKSPACE_HASH }}
- name: Run Forge coverage for zero-ex
working-directory: contracts/zero-ex
run: |
forge coverage --report summary --report lcov
- if: ${{ steps.check-for-image.outputs.image-exists == false }}
name: Built image message
run: |
echo "### :floppy_disk: An image was built for ${{ env.WORKSPACE_NAME}} @ ${{ env.WORKSPACE_HASH }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Access this image at" >> $GITHUB_STEP_SUMMARY
echo "\`${{ env.REPOSITORY_URI }}:${{ env.WORKSPACE_NAME }}__${{ env.WORKSPACE_HASH }}\`" >> $GITHUB_STEP_SUMMARY
echo "or" >> $GITHUB_STEP_SUMMARY
echo "\`${{ env.REPOSITORY_URI }}:${{ env.WORKSPACE_NAME }}_${{ env.COMMIT_SHA }}\`" >> $GITHUB_STEP_SUMMARY
- name: Upload the coverage report to Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
base-path: ./contracts/zero-ex/
path-to-lcov: ./contracts/zero-ex/lcov.info
- name: Comment on the PR that the docker image was built
if: github.event_name == 'pull_request' && steps.check-for-image.outputs.image-exists == false
uses: thollander/actions-comment-pull-request@v2.3.1
with:
message: |
### :floppy_disk: An image was built for ${{ env.WORKSPACE_NAME}} @ ${{ env.WORKSPACE_HASH }}
Access this image at
`${{ env.REPOSITORY_URI }}:${{ env.WORKSPACE_NAME }}__${{ env.WORKSPACE_HASH }}`
or
`${{ env.REPOSITORY_URI }}:${{ env.WORKSPACE_NAME }}_${{ env.COMMIT_SHA }}`
- name: Check coverage threshold
uses: VeryGoodOpenSource/very_good_coverage@v2
with:
path: ./contracts/zero-ex/lcov.info
min_coverage: 6.98
exclude: '**/tests'
summarize_images:
env:
REPOSITORY_URI: 883408475785.dkr.ecr.us-east-1.amazonaws.com/apps
permissions:
id-token: write
contents: write
pull-requests: write
# TODO (rhinodavid): Consider separating this into a reusable workflow
# https://docs.github.com/en/actions/using-workflows/reusing-workflows#creating-a-reusable-workflow
name: Summarize Images
needs: [build_and_test, build_images]
- name: Run Forge build on governance contracts
working-directory: ./contracts/governance
run: |
forge --version
forge build --sizes
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: arn:aws:iam::883408475785:role/github-actions
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# TODO: re-enable once the issue is resolved.
# - name: Run Forge tests on governance contracts
# working-directory: ./contracts/governance
# run: |
# forge test -vvv --gas-report
# - name: Run Forge coverage on governance contracts
# working-directory: ./contracts/governance
# run: |
# forge coverage --report lcov
# - name: Upload the coverage report to Coveralls
# uses: coverallsapp/github-action@master
# with:
# github-token: ${{ secrets.GITHUB_TOKEN }}
# base-path: ./contracts/governance/
# path-to-lcov: ./contracts/governance/lcov.info
- name: Summarize images for apps-node
id: summarize-images
uses: ./packages/image-summary-action
with:
dry-run-result: ${{ needs.build_and_test.outputs.turbo_info }}
dir: apps-node
uri-base: ${{ env.REPOSITORY_URI }}

154
.github/workflows/deploy-api.yml vendored Normal file
View File

@ -0,0 +1,154 @@
# Deploy Swap Service and a few others: meta transaction service, RFQ cache worker, and etc.
name: Deploy Swap Service
on:
workflow_dispatch:
inputs:
image_identifier:
description: Image identifier, e.g. api__2d5f8eed5287b74b or api_fd9c069a7d8a1bfdfca9a3c08cb9f405bfebdcc6
type: string
required: true
environment:
description: Environment(s) to deploy to
type: choice
options:
- prod-all
- prod-arbitrum
- prod-avalanche
- prod-bsc
- prod-celo
- prod-ethereum
- prod-fantom
- prod-optimism
- prod-polygon
- staging-all
- staging-bsc
- staging-polygon
default: prod-all
jobs:
deploy:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 1
# First, make sure the image we're attempting to deploy exists
- name: Set ECR image tag
run: echo "ECR_IMAGE_TAG=${{ inputs.image_identifier }}" >> $GITHUB_ENV
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: arn:aws:iam::883408475785:role/github-actions
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Check for existing images
id: check-for-image
run: ./.github/scripts/check-for-image.sh
shell: bash
- if: ${{ steps.check-for-image.outputs.image-exists == false }}
name: Missing image
run: |
echo "Image with tag ${{ inputs.image_identifier }} does not exist"
exit 1
shell: bash
- name: Create configuration
uses: rishabhgupta/split-by@v1
id: conf
with:
string: ${{ inputs.environment }}
split-by: '-'
- name: Create environment variables (ethereum)
run: |
echo "folder_env=${{ steps.conf.outputs._0 }}" >> $GITHUB_ENV
echo "chain=${{ steps.conf.outputs._1 }}" >> $GITHUB_ENV
#
# Make the changes to 0x-main-infra
# - For `kustomization.yaml` based one's the image `name` needs to change to `883408475785.dkr.ecr.us-east-1.amazonaws.com/app`
# - For other yml files image prefix needs to change (.../0x/api -> .../app)
#
- name: Checkout 0x-main-infra
uses: actions/checkout@v3
with:
token: ${{ secrets.PAT }}
repository: 0xProject/0x-main-infra
- name: Update API configs (Ethereum)
if: env.chain == 'ethereum' || env.chain == 'all'
run: |
sed -i -e 's/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:api_.*"/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:${{ inputs.image_identifier }}"/g' env/0x-api/${{ env.folder_env }}/common/*.yml env/0x-api/${{ env.folder_env }}/apis/*.yml
- name: Update API configs (Ethereum Goerli)
if: env.chain == 'ethereum' || (env.chain == 'all' && env.folder_env == 'prod')
run: |
sed -i -e 's/newTag:.*/newTag: ${{ inputs.image_identifier }}/g' clusters/main-cluster/kubernetes/goerli-api/kustomization.yaml
- name: Update API configs (Arbitrum)
if: env.chain == 'arbitrum' || (env.chain == 'all' && env.folder_env == 'prod')
run: |
sed -i -e 's/newTag:.*/newTag: ${{ inputs.image_identifier }}/g' clusters/main-cluster/kubernetes/arbitrum-api/kustomization.yaml
- name: Update API configs (BSC)
if: env.chain == 'bsc' || env.chain == 'all'
run: |
sed -i -e 's/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:api_.*"/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:${{ inputs.image_identifier }}"/g' env/0x-api/${{ env.folder_env }}/apis/bsc/*.yml
- name: Update API configs (Polygon)
if: env.chain == 'polygon' || env.chain == 'all'
run: |
sed -i -e 's/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:api_.*"/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:${{ inputs.image_identifier }}"/g' env/0x-api/${{ env.folder_env }}/apis/polygon/*.yml
- name: Update API configs (Polygon Mumbai)
if: env.chain == 'polygon' || (env.chain == 'all' && env.folder_env == 'prod')
run: |
sed -i -e 's/newTag:.*/newTag: ${{ inputs.image_identifier }}/g' clusters/main-cluster/kubernetes/mumbai-api/kustomization.yaml
- name: Update API configs (Avalanche) || env.chain == 'all'
if: env.chain == 'avalanche' || (env.chain == 'all' && env.folder_env != 'staging')
run: |
sed -i -e 's/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:api_.*"/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:${{ inputs.image_identifier }}"/g' env/0x-api/${{ env.folder_env }}/apis/avalanche/*.yml
- name: Update API configs (Fantom)
if: env.chain == 'fantom' || (env.chain == 'all' && env.folder_env != 'staging')
run: |
sed -i -e 's/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:api_.*"/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:${{ inputs.image_identifier }}"/g' env/0x-api/${{ env.folder_env }}/apis/fantom/*.yml
- name: Update API configs (Celo)
if: env.chain == 'celo' || (env.chain == 'all' && env.folder_env != 'staging')
run: |
sed -i -e 's/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:api_.*"/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:${{ inputs.image_identifier }}"/g' env/0x-api/${{ env.folder_env }}/apis/celo/*.yml
- name: Update API configs (Optimism)
if: env.chain == 'optimism' || (env.chain == 'all' && env.folder_env != 'staging')
run: |
sed -i -e 's/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:api_.*"/"883408475785\.dkr\.ecr\.us-east-1\.amazonaws\.com\/apps\:${{ inputs.image_identifier }}"/g' env/0x-api/${{ env.folder_env }}/apis/optimism/*.yml
# Remove special characters from the image identifier so it can be used as a branch name
- uses: yeouchien/sanitize-branch-name-action@v1
name: Sanitize branch name
id: branch
with:
branch-name: '${{ inputs.image_identifier }}'
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.PAT }}
author: ${{ github.event.sender.login }} <${{ github.event.sender.login }}@users.noreply.github.com>
commit-message: 'api/${{ env.chain }}/${{ env.folder_env }}: Deploy ${{ inputs.image_identifier }}'
title: '[api][${{ env.chain }}][${{ env.folder_env }}] Deploy ${{ inputs.image_identifier }}'
branch: '0xApi/${{ env.chain }}/${{ env.folder_env }}/${{ steps.branch.outputs.sanitized-branch-name }}'
body: |
## Automated deploy action for the workspace: API
Triggered by @${{ github.event.sender.login }}

97
.github/workflows/deploy-rfq-api.yml vendored Normal file
View File

@ -0,0 +1,97 @@
# Deploy RFQ API Service
name: Deploy RFQ API Service
on:
workflow_dispatch:
inputs:
image_identifier:
description: Image identifier, e.g. rfq-api__2d5f8eed5287b74b or rfq-api_fd9c069a7d8a1bfdfca9a3c08cb9f405bfebdcc6
type: string
required: true
environment:
description: Environment(s) to deploy to
type: choice
options:
- staging
- production
required: true
env:
IMAGE_NAME: 883408475785.dkr.ecr.us-east-1.amazonaws.com/apps
jobs:
deploy:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 1
# First, make sure the image we're attempting to deploy exists
- name: Set ECR image tag
run: echo "ECR_IMAGE_TAG=${{ inputs.image_identifier }}" >> $GITHUB_ENV
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: arn:aws:iam::883408475785:role/github-actions
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Check for existing images
id: check-for-image
run: ./.github/scripts/check-for-image.sh
shell: bash
- if: ${{ steps.check-for-image.outputs.image-exists == false }}
name: Missing image
run: |
echo "Image with tag ${{ inputs.image_identifier }} does not exist"
exit 1
shell: bash
#
# Make the changes to 0x-main-infra
#
- name: Checkout 0x-main-infra
uses: actions/checkout@v3
with:
token: ${{ secrets.PAT }}
repository: 0xProject/0x-main-infra
- name: Update GitOps Repo
id: update_gitops_repo
run: |
cd 'clusters/main-cluster/kubernetes/rfq-${{ inputs.environment }}'
# Read current image tag
current_image_identifier=$(yq e '.images[] | select(.name == "0x-rfq-api") | .newTag' kustomization.yaml)
# Update image tags
kustomize edit set image 0x-rfq-api=${IMAGE_NAME}:${{ inputs.image_identifier }}
kustomize edit set image 0x-rfq-api-worker=${IMAGE_NAME}:${{ inputs.image_identifier }}
# Output for next steps
echo "##[set-output name=current_image_identifier;]${current_image_identifier}"
# Remove special characters from the image identifier so it can be used as a branch name
- uses: yeouchien/sanitize-branch-name-action@v1
name: Sanitize branch name
id: branch
with:
branch-name: '${{ inputs.image_identifier }}'
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.PAT }}
author: ${{ github.event.sender.login }} <${{ github.event.sender.login }}@users.noreply.github.com>
commit-message: 'rfq-${{ inputs.environment }}: Deploy ${{ inputs.image_identifier }}'
title: '[rfq-api][${{ inputs.environment }}] Deploy ${{ inputs.image_identifier }}'
branch: '0xRfqApi/${{ inputs.environment }}/${{ steps.branch.outputs.sanitized-branch-name }}'
body: |
## Automated deploy action for the workspace: API
Triggered by @${{ github.event.sender.login }}
Comparison: https://github.com/${{ github.repository }}/compare/${{ steps.update_gitops_repo.outputs.current_image_identifier }}...${{ inputs.image_identifier }}

View File

@ -1,77 +0,0 @@
name: publish
on:
workflow_dispatch:
inputs:
ci_status:
description: 'required CI status'
default: 'success'
required: true
prerelease:
description: 'prerelease name'
required: false
jobs:
publish:
runs-on: ubuntu-latest
env:
PublishBranch: publish/${{github.ref_name }}-${{ github.run_id }}-${{ github.run_number }}
steps:
- name: 'check successful status'
run: |
REF_STATUS=$(curl -s \
'https://api.github.com/repos/${{ github.repository }}/commits/${{ github.ref }}/status' \
| jq .state)
[[ "${REF_STATUS}" == '"${{ github.event.inputs.ci_status }}"' ]] || \
(echo "::error ::${{ github.ref }} does not have a successful CI status" && false)
- name: Add foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- uses: actions/checkout@v2
with:
ref: ${{ github.ref }}
fetch-depth: 0
- uses: actions/setup-node@v1
with:
node-version: 16
- uses: actions/setup-python@v2
- name: 'configure git'
run: |
git config --global user.email "github-actions@github.com"
git config --global user.name "Github Actions"
- name: 'Checkout new branch'
run: |
git checkout -b $PublishBranch
git push -u origin $PublishBranch
- name: 'install dependencies'
run: |
yarn -D
- name: 'build and publish'
run: |
echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc
npm run run:publish:gha
env:
NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
PUBLISH_PRERELEASE: ${{ github.event.inputs.prerelease }}
- name: 'Create PR to merge into ref branch'
run: |
gh pr create \
-B ${{ github.ref_name }} \
-H $PublishBranch \
--title "Publish: CHANGELOG and Package Version Updates into ${{ github.ref_name }}" \
--body "Syncing CHANGELOG and package version updates from publish action ${{github.run_id}}-${{github.run_number}} into ${{ github.ref_name}} branch" \
--reviewer ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ github.token }}
- name: 'Create PR to merge ref branch into main'
run: |
gh pr create \
-B main \
-H ${{ github.ref_name }} \
--title "Publish: Sync ${{ github.ref_name }} into main " \
--body "Syncing ${{ github.ref_name }} back into main after publish action. NOTE: this PR should be merged after CHANGELOG and package version updates have been merged into ${{ github.ref_name }}" \
--reviewer ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ github.token }}

View File

@ -1,40 +0,0 @@
name: "Close stale issues and PRs"
on:
schedule:
- cron: "0 8 * * 1-5" # This is in UTC.
# Do a dry-run (debug-only: true) whenever this workflow itself is changed.
pull_request:
paths:
- .github/workflows/stale.yml
types:
- opened
- synchronize
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v7
with:
ascending: true # Spend API operations budget on older, more-likely-to-get-closed issues first
close-issue-message: "" # Leave no comment when closing
close-pr-message: "" # Leave no comment when closing
days-before-issue-stale: 30
days-before-pr-stale: 30
days-before-close: 14
debug-only: ${{ github.event_name == 'pull_request' }} # Dry-run when true.
exempt-issue-labels: bug,enhancement,feature,question
# No actual changes get made in debug-only mode, so we can raise the operations ceiling.
operations-per-run: ${{ github.event_name == 'pull_request' && 100 || 30}}
stale-issue-label: stale
stale-issue-message: "This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions."
stale-pr-label: stale
stale-pr-message: "This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions."
# Time immemorial when in debug-only mode (ie. on pull requests).
# `STALEBOT_START_DATE` otherwise.
# You can use this as a killswitch by setting `STALEBOT_START_DATE` in the far future.
start-date: ${{ github.event_name == 'pull_request' && '1970-01-01T00:00:00Z' || secrets.STALEBOT_START_DATE }} # ISO 8601 or RFC 2822

145
.gitignore vendored
View File

@ -1,126 +1,35 @@
# Logs
logs
*.log
npm-debug.log*
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
# misc
.DS_Store
*.pem
.idea
# debug
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# SQLite database files
*.db
*.sqlite
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# NVM config
.nvmrc
# Optional npm cache directory
.npm
.npmrc
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
# env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# built library using in commonjs module syntax
contracts/erc20/lib/
contracts/test-utils/lib/
contracts/treasury/lib/
contracts/utils/lib/
contracts/zero-ex/lib/
packages/contract-addresses/lib/
packages/contract-artifacts/lib/
packages/contract-wrappers/lib/
packages/protocol-utils/lib/
# logs
*.log
# UMD bundles that export the global variable
_bundles
# turbo
.turbo
# generated documentation
generated_docs/
# other package managers
package-lock.json
pnpm-lock.yaml
TODO.md
# IDE file
.vscode
.idea
*~
.\#*
\#*\#
# generated contract artifacts/
generated-artifacts/
# generated contract wrappers
generated-wrappers/
# forge std-lib
contracts/zero-ex/contracts/deps/forge-std
# foundry artifacts
foundry-artifacts/
# foundry cache
cache/
foundry-cache/
#foundry output artifacts
out/
# typechain wrappers
contracts/zero-ex/typechain-wrappers/
# foundry packages
contracts/governance/cache
contracts/governance/out
# Doc README copy
packages/*/docs/README.md
.DS_Store
*~
\#*\#
.\#*
# the snapshot that gets built for migrations sure does have a ton of files
packages/migrations/0x_ganache_snapshot*
# build
__build__
dist
out

22
.gitmodules vendored
View File

@ -1,17 +1,9 @@
[submodule "contracts/zero-ex/contracts/deps/forge-std"]
path = contracts/zero-ex/contracts/deps/forge-std
[submodule "apps-node/api/contracts/lib/forge-std"]
path = apps-node/api/contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/erc20/lib/forge-std"]
path = contracts/erc20/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/governance/lib/forge-std"]
path = contracts/governance/lib/forge-std
[submodule "apps-node/pool-cache/lib/forge-std"]
path = apps-node/pool-cache/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/governance/lib/openzeppelin-contracts"]
path = contracts/governance/lib/openzeppelin-contracts
url = https://github.com/openzeppelin/openzeppelin-contracts
[submodule "contracts/governance/lib/openzeppelin-contracts-upgradeable"]
path = contracts/governance/lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/openzeppelin-contracts-upgradeable"]
branch = v4.8.2
[submodule "apps-node/pool-cache/lib/solmate"]
path = apps-node/pool-cache/lib/solmate
url = https://github.com/transmissions11/solmate

View File

@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged --no-stash

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v18.14.1

View File

@ -1,10 +1,7 @@
lib
deps
.nyc_output
generated-artifacts/
generated-wrappers/
foundry-artifacts/
out/
cache/
package.json
packages
**/dist/
lib/
__build__/
node_modules/
# Github Actions
**/action/**/*.js

View File

@ -1,20 +1,21 @@
{
"printWidth": 120,
"tabWidth": 4,
"printWidth": 120,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "avoid",
"arrowParens": "always",
"overrides": [
{
"files": "**/*.sol",
"options": {
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false
}
"files": "*.sol",
"options": {
"singleQuote": false
}
},
{
"files": "*.yml",
"options": {
"tabWidth": 2
}
}
]
]
}

View File

@ -1,23 +0,0 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# Build documentation with MkDocs
#mkdocs:
# configuration: mkdocs.yml
# Optionally build your docs in additional formats such as PDF
#formats:
# - pdf
# Optionally set the version of Python and requirements required to build your docs
python:
version: 3.7
install:
- requirements: docs/requirements.txt

View File

@ -1,17 +0,0 @@
{
"extends": "solhint:recommended",
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
"avoid-low-level-calls": "off",
"avoid-tx-origin": "warn",
"code-complexity": "off",
"const-name-snakecase": "error",
"function-max-lines": ["error", 350],
"max-line-length": ["error", 120],
"no-inline-assembly": "off",
"quotes": ["error", "double"],
"no-empty-blocks": "off",
"compiler-version": "off"
}
}

View File

@ -1,9 +0,0 @@
contracts/erc20/src/ZRXToken.sol
node_modules/
lib
deps
generated-artifacts/
generated-wrappers/
foundry-artifacts/
out/
cache/

23
PULL_REQUEST_TEMPLATE.md Normal file
View File

@ -0,0 +1,23 @@
# Context
<!-- Familiarize your reviewers with the circumstances that make this PR necessary.
For example, "As the number of market makers have increased, we've seen an uptick in
request latency" -->
# Description
<!-- Describe the changes you've made in this PR in response to the context. Include
a reference to the related issue. For example, "Create a cache to store market maker
prices. The values in the cache are invalidated after ten seconds. Closes MKR-123" -->
# Testing
<!-- Describe the automated and manual testing you've done with these changes with
enough detail that a reviewer can replicate the tests. For example, "Added a unit test
for the cache & ran code locally with a debugger attached to verify proper functionality" -->
# TODO
<!-- Give the reviewer an idea of what the next steps are relating to the problem(s)
you outlined in the Context section, if any. For example, "Watch grafana dashboards
to confirm latency impact" -->

554
README.md
View File

@ -1,169 +1,435 @@
<img src="https://github.com/0xProject/branding/blob/master/0x%20Logo/PNG/0x-Logo-Black.png" width="150px" >
**`0x-labs` is the home of 0x's private codebase.**
---
# Working in the repository
[0x][website-url] is an open protocol that facilitates trustless, low friction exchange of Ethereum-based assets. For more information on how it works, check out the [0x protocol specification](https://protocol.0x.org/).
## Technology
This repository is a monorepo including the 0x protocol smart contracts and numerous developer tools. Each public sub-package is independently published to NPM.
The repository relies on the following technologies:
[website-url]: https://0x.org
- **[Yarn classic](https://classic.yarnpkg.com/)**: A JavaScript package manager. `0x-labs` is built on [Yarn workspaces](https://classic.yarnpkg.com/lang/en/docs/workspaces/).
- **[Turborepo](https://turbo.build/repo)**: Provides a caching solution to speed up workflows (`build`, `test`, etc.)
[![Coverage Status](https://coveralls.io/repos/github/0xProject/protocol/badge.svg?branch=development)](https://coveralls.io/github/0xProject/protocol?branch=development)
[![Discord](https://img.shields.io/badge/chat-discord.chat-yellow.svg?style=flat)](https://discordapp.com/invite/d3FTX3M)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
> Tip: the Turborepo [Monorepo Handbook](https://turbo.build/repo/docs/handbook) explains many of the concepts
> you'll want to know to work in the repository
## Packages
## Remote caching
Visit our [developer portal](https://0x.org/docs/) for a comprehensive list of core & community maintained packages. All packages maintained with this monorepo are listed below.
Turborepo has a [remote caching feature](https://turbo.build/repo/docs/core-concepts/remote-caching) which allows build artifacts
to be cached and shared between your local machine, other developers' machines, and CI. This means that if you've built and tested
your changes locally, they won't need to be rebuilt and retested in CI.
### Solidity Packages
These packages are all under development. See [/contracts/README.md](/contracts/README.md) for a list of deployed packages.
| Package | Version | Description |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| [`@0x/contracts-zero-ex`](/contracts/zero-ex) | [![npm](https://img.shields.io/npm/v/@0x/contracts-zero-ex.svg)](https://www.npmjs.com/package/@0x/contracts-zero-ex) | The contracts used for settling trades within the protocol |
| [`@0x/contracts-erc20`](/contracts/erc20) | [![npm](https://img.shields.io/npm/v/@0x/contracts-erc20.svg)](https://www.npmjs.com/package/@0x/contracts-erc20) | Implementations of various ERC20 tokens |
| [`@0x/contracts-test-utils`](/contracts/test-utils) | [![npm](https://img.shields.io/npm/v/@0x/contracts-test-utils.svg)](https://www.npmjs.com/package/@0x/contracts-test-utils) | TypeScript/Javascript shared utilities used for testing contracts |
| [`@0x/contracts-utils`](/contracts/utils) | [![npm](https://img.shields.io/npm/v/@0x/contracts-utils.svg)](https://www.npmjs.com/package/@0x/contracts-utils) | Generic libraries and utilities used throughout all of the contracts |
### TypeScript/Javascript Packages
#### 0x-specific packages
| Package | Version | Description |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| [`@0x/protocol-utils`](/packages/protocol-utils) | [![npm](https://img.shields.io/npm/v/@0x/protocol-utils.svg)](https://www.npmjs.com/package/@0x/protocol-utils) | A set of utilities for generating, parsing, signing and validating 0x orders |
| [`@0x/contract-addresses`](/packages/contract-addresses) | [![npm](https://img.shields.io/npm/v/@0x/contract-addresses.svg)](https://www.npmjs.com/package/@0x/contract-addresses) | A tiny utility library for getting known deployed contract addresses for a particular network. |
| [`@0x/contract-wrappers`](/packages/contract-wrappers) | [![npm](https://img.shields.io/npm/v/@0x/contract-wrappers.svg)](https://www.npmjs.com/package/@0x/contract-wrappers) | JS/TS wrappers for interacting with the 0x smart contracts |
| [`@0x/contract-artifacts`](/packages/contract-artifacts) | [![npm](https://img.shields.io/npm/v/@0x/contract-artifacts.svg)](https://www.npmjs.com/package/@0x/contract-artifacts) | 0x smart contract compilation artifacts | |
## Usage
Node version 6.x or 8.x is required.
Most of the packages require additional typings for external dependencies.
You can include those by prepending the `@0x/typescript-typings` package to your [`typeRoots`](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html) config.
```json
"typeRoots": ["node_modules/@0x/typescript-typings/types", "node_modules/@types"],
```
## Contributing
We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.
#### Read our [contribution guidelines](.github/CONTRIBUTING.md).
### Install dependencies
Make sure you are using Yarn v1.9.4. To install using brew:
To enable remote caching on your machine, you'll need to be a member of the [Vercel 0x-eng team](https://vercel.com/0x-eng). Once you've
been added, run:
```bash
brew install yarn@1.9.4
yarn turbo login
yarn turbo link
```
Then install dependencies
> Tip: after the `link` script asks "Would you like to enable Remote Caching for ~/0x-labs?", make sure to
> select the `0x-eng` team, _not_ your personal account.
```bash
## Depending on shared code
See the [Internal Packages](https://turbo.build/repo/docs/handbook/sharing-code/internal-packages) section of the Turborepo
Monorepo Handbook.
## Working with workspaces
If you're only dealing with one workspace, _and_ all its dependencies have been built,
you can `cd` into the workspace root and mostly ignore that you're in a monorepo:
```sh
cd apps-node/api
yarn install
```
### Build
To build all packages:
```bash
yarn build
```
To build a specific package:
```bash
PKG=@0x/protocol-utils yarn build
```
To build all contracts packages:
```bash
yarn build:contracts
```
### Watch
To re-build all packages on change:
```bash
yarn watch
```
To watch a specific package and all it's dependent packages:
```bash
PKG=[NPM_PACKAGE_NAME] yarn watch
e.g
PKG=@0x/protocol-utils yarn watch
```
### Clean
Clean all packages:
```bash
yarn clean
```
Clean a specific package
```bash
PKG=@0x/protocol-utils yarn clean
```
### Rebuild
To re-build (clean & build) all packages:
```bash
yarn rebuild
```
To re-build (clean & build) a specific package & it's deps:
```bash
PKG=@0x/protocol-utils yarn rebuild
```
### Lint
Lint all packages:
```bash
yarn lint
```
Lint a specific package:
```bash
PKG=@0x/protocol-utils yarn lint
```
### Run Tests
Run all tests:
```bash
yarn test
```
Run a specific package's test:
> 👆🏾 Note: Working in a specific workspace this way won't get you any of the
> caching benefits of Turborepo.
Working from the repository root allows you perform actions on some or all
workspaces.
Use [`yarn workspace <workspace_name> <command>`](https://classic.yarnpkg.com/en/docs/cli/workspace#toc-yarn-workspace)
to run a command in a specific workspace:
```bash
PKG=@0x/protocol-utils yarn test
pwd # ~/0x-labs
yarn workspace api add -D ts-node
```
Run all contracts packages tests:
Use [`yarn workspaces <command>`](https://classic.yarnpkg.com/en/docs/cli/workspaces#toc-yarn-workspaces-info)
to run a script in _every_ workspace, if it is defined:
```bash
yarn test:contracts
pwd # ~/0x-labs
yarn workspaces fix
```
To perform complex actions across workspaces, we use Turborepo
[pipelines](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks).
Turborepo pipelines are cached for speed and allow dependencies between pipelines
to be specified. For example:
- "run `test` in every workspace that has one, but run `build` in each of those workspaces first"
- "`build` each workspace, but make sure `build` is first run on any [dependencies](#depending-on-shared-code) within the repository"
Pipelines are specified in `turbo.json`. Each pipeline can be run with `yarn <pipeline name>` via the
scripts defined in the root `package.json`.
For example, the `test:ci` pipeline first ensures each workspace under test has been built,
then runs the `test:ci` script in each workspace that has one defined:
```bash
pwd # ~/0x-labs
yarn test:ci # runs script `turbo run test:ci`
```
To run a Turbo pipeline on a single workspace, use the `--filter` flag:
```bash
pwd # ~/0x-labs
yarn build --filter=api # Builds workspace "api" and its dependencies
```
> Tip: The `--filter` flag has syntax to match on a number of dimensions. See
> ["Filtering Workspaces"](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
> for more.
# Structure
Code in the repository is organized into "workspaces", which are directories with a `package.json` file in the root.
Workspaces are created for logical pieces of architecture: servers, websites, shared libraries, etc., and
are _not_ organized based on team structure.
Files in the root directory contain code that either (a) applies to **all** workspaces in the repository or
(b) is necessary for repository operation. Generally, adding a new workspace should not involve any changes
to files in the root directory.
For example, if your workspace contains an `.xyz/` directory which shouldn't be included in git, then
`.xyz/*` shall be added to the `.gitignore` _in the workspace root_, not in the root `.gitignore`.
Workspaces exist in the following directories:
- `apps-node`
- `packages`
- `sites`
## `apps-node`
Workspaces in the `apps-node` directory **have a Docker image built for each** using `.github/Dockerfile-node`.
The image is uploaded to AWS Elastic Container Registry and is tagged with both the commit hash of the commit where
the image was built and the Turborepo hash of the workspace. Either of these tags can be used in `0x-main-infra`
to specify the image to use.
If there are no changes to a workspace, then no new image is created.
As an example, a new image for `my-app` could be accessed at:
- `***.dkr.ecr.us-east-1.amazonaws.com/apps:my-app__789355d868cd646f` (Turborepo hash, note the double underscore)
- `***.dkr.ecr.us-east-1.amazonaws.com/apps:my-app_2a4810fbd3f195bf8da8c161d7d5b03e9626cd2e` (Commit hash)
> Tip: The GitHub bot will comment on each commit in a PR or merged to `main` with the image tags that correspond to
> that commit. Look for the word bubble icon 💬 where a commit is mentioned in a PR.
## `packages`
Workspaces in `packages` contain shared code meant to be used by app and website workspaces. They do
not create any "runnable" output.
See [Depending on shared code](#Depending-on-shared-code) for more on how to use packages.
## `sites`
Workspaces in `sites` represent a type of app which will be deployed through means other than
a Docker image, most commonly Vercel or similar.
Functionally, there is no difference between workspaces in `packages` and in `sites`.
# CI & pipelines
GitHub Actions runs the following pipelines on each pull request and commit to the repository:
- `build`
- `build:no-diff`
- `test:ci`
- `lint:ci`
Each pipeline will run the corresponding script in the `package.json` of each workspace, if
it exists. To pass CI, each pipeline must finish with a `0` exit code. Additionally, the
`build:no-diff` pipeline must not produce any new build artifacts.
> ❓ FYI: There are some advanced use cases, such as auto-generating documentation, where one
> might want to run a build step and commit the output. CI will run `git diff --exit-code` after
> `build:no-diff`, which will return an exit code `1`, and thus fail CI, if `build:no-diff` produced
> outputs that were not included in the PR.
# Configuration
- The primary branch of the repository is `main`
- `main` is protected from pushes
- `main` has a linear commit history
- Commits to `main` are accomplished by a pull request (PR)
- PRs require an approval to submit
- PRs require [CI](#ci--pipelines) to pass to submit
- PRs are submitted via “Squash and Merge”. One PR will translate to one commit.
- Commit messages should be in the form of a present-tense “action”, i.e. `Add prometheus metric for TokenPriceOracle`
- Commit messages may be prefixed with one or more “tags” describing the portions of the codebase the commit affects, i.e. `[rfqm] Add support for BSC`
- A [`CODEOWNERS`](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) file may be used to require PR approvals from specific people for specific sub-directories of the repository
# Creating workspaces
At the most basic level, creating a workspace is as simple as adding a directory under the appropriate
[top-level](#structure) directory and adding a `package.json`.
However, there are some conventions meant to make sure that workspaces play well with each other in
the repository:
- The workspace name (`name` field in `package.json`) matches the top-level directory name of the workspace
- The workspace version (`version` field in `package.json`) is `0.0.0`. Since no workspace is published as an npm package, there is no concept of a "version".
- The workspace `private` field is set to `true` to prevent `yarn` from complaining
- Source files are located in the `src/` directory
- Test files are located in either a `test/` or `__tests__` directory, or a nested child directory thereof
- Build artifacts not committed to the repository are written to the `__build__`, `dist`, or `out` directory in the packages top level folder
Some other things to keep in mind:
The CI runs the scripts specified in the [_CI & pipelines_](#ci--pipelines) section. If your repository needs some
special check in CI, make sure to run it as part of one of the CI checks:
```json
{
"scripts": {
"circular": "madge --circular --extensions ts ./",
"lint": "eslint .",
"format": "prettier --list-different --config .prettierrc",
"lint:ci": "yarn circular && yarn format && yarn lint"
}
}
```
If your workspace needs to deviate from the conventions above, see
["Configuring Workspaces"](https://turbo.build/repo/docs/core-concepts/monorepos/configuring-workspaces)
> ⛔️ Warning: some 0x projects write their build outputs to `lib`. This collides with conventions
> of many frameworks (i.e. Foundry, SvelteKit, Rust). Make sure to change the build target from `lib`
> before migrating the project into `0x-labs`.
## Common tooling
For the most part, you can use whatever tooling you'd like in your workspace. However, `0x-labs` has
some built in tooling setup which can make bootstrapping your workspace easier.
### Prettier
Prettier is enabled globally and is a root dependency of `0x-labs`. (This isn't ideal, but it's a limitation
of Prettier as of February 2023).
To use Prettier in your workspace, first setup a `.prettierignore` in the workspace root. Note that, unlike
`.gitignore` files, `.prettierignore` files do not "extend" `.prettierignore` files higher up the directory
tree. (See ["Feature: handle .prettierignore location like .gitignore and .npmignore (#4081)"](https://github.com/prettier/prettier/issues/4081))
Next, add scripts as you'd like to your `package.json`. Some suggestions:
- `"fix": "prettier --write ."` (Available via a turbo pipeline from the repo root)
- `"lint:ci": "prettier --check ."` (You probably also want `eslint` here)
### `tsconfig` and eslint
The repository has common configurations for both [TypeScript](packages/tsconfig/README.md)
and [eslint](packages/eslint-config-common/README.md). See the linked READMEs for setup instructions.
## Websites
Since Vercel is the author of Turborepo, it's no surprise that deploying websites from the
repository to Vercel is a cinch. Key points of the Vercel project settings follow:
- Set "General > Root Directory" to the workspace directory, e.g. `sites/matcha`
- In "General > Root Directory" ensure that "Include source files outside of the Root Directory in the Build Step" is checked
- Set "Git > Production Branch" to `main` (assuming you want commits to `main` to go into production)
- Set "Git > Ignored Build Step" to `npx turbo-ignore`. This causes commits to `main` which _don't_ affect your site to not trigger a new production deployment.
That's it! For more information, see the Vercel [Monorepo](https://vercel.com/docs/concepts/monorepos)
and the [Ignored Build Step](https://vercel.com/docs/concepts/projects/overview#ignored-build-step) documentation.
## Running non-node binaries
Consider the scenario where a project wishes to run Foundry tests in CI.
```json
{
"scripts": {
"test:ci": "forge test -vvv"
}
}
```
The CI machine will have `forge` installed, so the test will run as expected.
Locally, developers would need `forge` installed to successfully run the
`test:ci` turbo pipeline, and this presents a problem. As the number of binaries
not installed by `yarn` increases, the developer would need to install more
and more binaries which they may not even need for the workspaces they work on.
The solution requires two steps:
1. The root `package.json` script to run each pipeline first runs `turbo-prerun.sh` (e.g. `"build": ". ./turbo-prerun.sh && turbo run build"`)
2. When a workspace `package.json` script requires an "external" binary, it is preceded
with `turbo-bin.sh` (e.g. `"build": "./../../turbo-bin.sh forge build"`)
In the first step, the `turbo-prerun.sh` runs the `--version` command
on the binaries specified at the bottom of `turbo-prerun.sh`.
It stores the output of `<bin> --version` as the environment variable `TURBO_VERSION_<bin>`,
if the binary is present.
This variable gets specified as part of the Turbo workspace hash, which ensures that
if the local version of the binary differs from the version in CI there won't be
a false cache hit.
In the second step, `turbo-bin.sh` looks for the env variable
`TURBO_VERSION_<bin>`. If it is present, the script continues to run as normal.
If the env variable is not present, the script exits with a code `0`.
This solution allows developers not working in specialized workspaces
to still be able to run and remote cache turbo pipelines, while
the complete pipeline runs in CI and locally if the necessary binaries
are installed.
### Setup
1. Create script in workspace `package.json`
2. Add binary to `turbo-prerun.sh`
3. Create Turbo [workspace configuration](https://turbo.build/repo/docs/core-concepts/monorepos/configuring-workspaces) with the appropriate environment variables
4. Add the binary to the CI setup
As an example, we'll add scripts requiring `forge` to a `foundry-demo` workspace:
(1) First we create a `package.json` file to name the workspace and specify the
scripts. Unfortunately, this is the only way to expose the scripts to turbo,
even if it isn't idiomatic for the language of the workspace.
Example package.json:
```json
{
"name": "foundry-demo",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "../../turbo-bin.sh forge build",
"test:ci": "../../turbo-bin.sh forge test"
}
}
```
(2) Next, we add a line to the bottom of `turbo-prerun.sh` to have it
check for `forge`:
```bash
########################################
# ADD MORE BINARIES BELOW AS NECESSARY #
########################################
set_binary_version forge
```
(3) In the workspace root, add a `turbo.json` to extend the root Turbo configuration.
In this configuration file, modify the pipeline tasks to include the environment variable for the `forge` binary. See the
[Turborepo docs: Altering Caching Based on Environment Variables](https://turbo.build/repo/docs/core-concepts/caching#altering-caching-based-on-environment-variables)
to learn more about how environment variables affect the pipeline.
```jsonc
// apps/foundry-demo/turbo.json
{
"extends": ["//"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["out/**"],
"env": ["TURBO_VERSION_FORGE"]
},
"test:ci": {
"dependsOn": ["build"],
"env": ["TURBO_VERSION_FORGE"]
}
}
}
```
(4) In `.github/workflows/ci.yml` `build_and_test` job, ensure the binary is installed:
```yaml
- name: Add foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
```
# Migrating existing repositories
See the [notes in Notion](https://www.notion.so/zeroex/Repository-consolidation-d5214657110841758e49d0aa5e032e0d?pvs=4#71e18cb0c3434e40a70cd336de3b25b4).
# Troubleshooting
## End of line sequences (CLRF, LF)
While we're on the verge of creating machines who surpass humans in intelligence,
we still haven't decided upon a single method to represent the end of a line in text files.
If you're having issues running commands, or `git status` lists file changes not staged
for commit which then go away when you `git add` them to stage, then you likely have files
with line endings which don't match your OS.
For example, this output of `yarn build` indicates the incorrect line ending
in `turbo-prerun.sh`:
```bash
$ . ./turbo-prerun.sh && turbo run build
: command not foundline 2:
: command not foundline 62:
'/turbo-prerun.sh: line 63: syntax error near unexpected token `{
'/turbo-prerun.sh: line 63: `set_binary_version() {
error Command failed with exit code 1.
```
These errors also show up in the logs as `^M` at the end of lines.
To understand the problem and how to configure `git` to avoid it,
see
["CRLF vs. LF: Normalizing Line Endings in Git"](https://www.aleksandrhovhannisyan.com/blog/crlf-vs-lf-normalizing-line-endings-in-git/).
To quickly fix individual files in VSCode, run "Change End of Line Sequence" in
the command pallet.
To bulk fix files, consider [`dos2unix`](https://stackoverflow.com/a/61030524/5840249).
## Caching issues
If you suspect a problem with Turborepo caching, you can disable it with the
[`--force` flag](https://turbo.build/repo/docs/reference/command-line-reference#--force):
```bash
yarn build --force
```
# Repo maintenance
## Remote caching
Accessing the Vercel remote cache in GitHub Actions requires the secrets
`TURBO_TEAM` and `TURBO_TOKEN` to be set in GitHub. Unfortunately, the
token is tied to specific user's account. If that person leaves the Vercel "0x-eng"
organization, a new token must be generated.
See the [Turborepo GitHub Actions CI Recipe for more](https://turbo.build/repo/docs/ci/github-actions#remote-caching).
## GitHub Personal Access Token
Some GitHub Actions, such as "Deploy" actions which publish PRs in other repositories, require a
[Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
stored in the repository secrets as `PAT`.
The current PAT is for the [0xEng](https://github.com/0xEng) account.
**The PAT expires on February 16th, 2024**

View File

@ -0,0 +1,5 @@
node_modules
postgres
.circleci
.git
.env

View File

@ -0,0 +1,32 @@
##### Required #####
CHAIN_ID=1
ETHEREUM_RPC_URL=
##### Recommneded #####
# To an appropriate endpoint based on `CHAIN_ID`
ZERO_EX_GAS_API_URL=https://gas.api.0x.org/source/median
##### Optional #####
HTTP_PORT=
SAMPLE_DISTRIBUTION_BASE=
FEE_RECIPIENT_ADDRESS=
TAKER_FEE_UNIT_AMOUNT=
POSTGRES_URI=
WHITELIST_ALL_TOKENS=
SWAP_IGNORED_ADDRESSES=
HTTP_KEEP_ALIVE_TIMEOUT=
HTTP_HEADERS_TIMEOUT=
SRA_ORDER_EXPIRATION_BUFFER_SECONDS=
SRA_PERSISTENT_ORDER_POSTING_WHITELISTED_API_KEYS=
MAX_EXPIRATION_ALERT_THRESHOLD_SECONDS=
META_TXN_RELAY_EXPECTED_MINED_SEC=
ENABLE_PROMETHEUS_METRICS=
PROMETHEUS_PORT=
ALT_RFQ_MM_ENDPOINT=
ALT_RFQ_MM_API_KEY=
INTEGRATORS_ACL=
KAFKA_BROKERS=
KAFKA_TOPIC_QUOTE_REPORT=
RFQM_MAINTENANCE_MODE=
INDICATIVE_PRICE_AWARE_RFQ_ENABLED=
FIRM_PRICE_AWARE_RFQ_ENABLED=

View File

@ -7,18 +7,25 @@
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"plugins": ["@typescript-eslint", "@zapper-fi", "no-only-tests"],
"ignorePatterns": [
"lib/**/*",
"migrations/*",
"generated-wrappers/**/*",
"generated-artifacts/**/*",
"test/generated-wrappers/**/*",
"test/generated-artifacts/**/*"
],
"rules": {}
"rules": {
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"@zapper-fi/ethereum-address": 2,
"no-only-tests/no-only-tests": "error"
}
}

1
apps-node/api/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.js linguist-generated

97
apps-node/api/.gitignore vendored Normal file
View File

@ -0,0 +1,97 @@
# Webstorm config folder
.idea
# VSCode config folder
.vscode
# dotenv environment variables file
.env
# Generated metadata file
metadata.json
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*_errors
*_logs
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# next.js build output
.next
# compiled ts sources
__build__
# sqlite DB
db/database.sqlite
# postgres docker volume
postgres
# Emacs org-mode projectile TODOs
TODOs.org
# macOS desktop services store
.DS_Store
TODO.txt
# Solidity artifacts and wrappers
**/generated-artifacts
**/generated-wrappers
# Foundry
foundry-artifacts
foundry-cache

3
apps-node/api/.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std

1
apps-node/api/.nvmrc Normal file
View File

@ -0,0 +1 @@
18.14.1

View File

@ -0,0 +1,10 @@
CHANGELOG.md
__build__
node_modules
generated-artifacts
generated-wrappers
generated-artifacts
generated-wrappers
foundry-artifacts
foundry-cache
forge-std

21
apps-node/api/.prettierrc Normal file
View File

@ -0,0 +1,21 @@
{
"tabWidth": 4,
"printWidth": 120,
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "always",
"overrides": [
{
"files": "*.sol",
"options": {
"singleQuote": false
}
},
{
"files": "*.yml",
"options": {
"tabWidth": 2
}
}
]
}

View File

@ -0,0 +1,3 @@
{
"ignore": "src/wrappers.ts|test/wrappers.ts|migrations|generated-wrappers|generated-artifacts|test/generated-wrappers|test/generated-artifacts"
}

BIN
apps-node/api/0x-api.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

207
apps-node/api/CHANGELOG.md Normal file
View File

@ -0,0 +1,207 @@
# [1.15.0](https://github.com/0xProject/0x-api/compare/v1.14.0...v1.15.0) (2020-08-17)
### Bug Fixes
* added new asset swapper ([#316](https://github.com/0xProject/0x-api/issues/316)) ([029a58a](https://github.com/0xProject/0x-api/commit/029a58a21e31a05cba06825824d05d1f2da4b5b4))
* excluded sources test ([#322](https://github.com/0xProject/0x-api/issues/322)) ([1327261](https://github.com/0xProject/0x-api/commit/13272610d206ba53b2f7b60d012bc0a0cd9a181b))
* lower db pool connections ([#318](https://github.com/0xProject/0x-api/issues/318)) ([d9cb04d](https://github.com/0xProject/0x-api/commit/d9cb04de8beec57584e3c68ecc67622905a107f6))
* lower db pool connections ([#319](https://github.com/0xProject/0x-api/issues/319)) ([bc9f218](https://github.com/0xProject/0x-api/commit/bc9f2184ac96d7b45b80a58be684f2e6b50622ae))
### Features
* Log QuoteReport to Kibana ([#298](https://github.com/0xProject/0x-api/issues/298)) ([7bd6d4d](https://github.com/0xProject/0x-api/commit/7bd6d4dec81abb09216cb8842f6de9adbf4d3a10))
# [1.14.0](https://github.com/0xProject/0x-api/compare/v1.13.0...v1.14.0) (2020-08-10)
### Bug Fixes
* handle addresses in depth ([#313](https://github.com/0xProject/0x-api/issues/313)) ([ce6978b](https://github.com/0xProject/0x-api/commit/ce6978b9cb3cc34c0b0354b0ef5309f3e87fb572))
* return input in depth-chart ([#307](https://github.com/0xProject/0x-api/issues/307)) ([a3fa961](https://github.com/0xProject/0x-api/commit/a3fa961dff4b25b60599e6ad18de20c780706de1))
* source regression ([#306](https://github.com/0xProject/0x-api/issues/306)) ([cdcfc78](https://github.com/0xProject/0x-api/commit/cdcfc78ed544524173afb60ea02eedc7e0c46d60))
* update asset-swapper optimizer for splits ([#311](https://github.com/0xProject/0x-api/issues/311)) ([5b212c9](https://github.com/0xProject/0x-api/commit/5b212c9c226ff952fe1b64db4fffe636967a25fe))
### Features
* Add support for buy token affiliate fees ([#310](https://github.com/0xProject/0x-api/issues/310)) ([2866253](https://github.com/0xProject/0x-api/commit/28662535489b07cd6d1f8971aa817ec8030413ef))
* ethToInputRate cost routing ([#315](https://github.com/0xProject/0x-api/issues/315)) ([e542c98](https://github.com/0xProject/0x-api/commit/e542c98e23a307ef0fada5ccdbea2d41d8a332fe))
# [1.13.0](https://github.com/0xProject/0x-api/compare/v1.12.0...v1.13.0) (2020-08-03)
### Bug Fixes
* Kovan deploy UniswapV2 ([#304](https://github.com/0xProject/0x-api/issues/304)) ([f4fb99b](https://github.com/0xProject/0x-api/commit/f4fb99b13bcd10dfbaa68a7160b677ae6ed9eda0))
* Kovan ERC20BridgeSampler ([#299](https://github.com/0xProject/0x-api/issues/299)) ([56f7a90](https://github.com/0xProject/0x-api/commit/56f7a9072ae3deb9264dbd8e291678dd75cffdea))
### Features
* added a unique identifier to the quote within the timestamp metadata … ([#281](https://github.com/0xProject/0x-api/issues/281)) ([d992563](https://github.com/0xProject/0x-api/commit/d992563bf4198d2b9b922b301133f33478083658))
* Rfqt included ([#293](https://github.com/0xProject/0x-api/issues/293)) ([965dedb](https://github.com/0xProject/0x-api/commit/965dedbe3c52c24b84fe2c3ad8ced5daa38eda67))
# [1.12.0](https://github.com/0xProject/0x-api/compare/v1.11.0...v1.12.0) (2020-07-27)
### Bug Fixes
* synchronization false unless tests ([#292](https://github.com/0xProject/0x-api/issues/292)) ([e050a22](https://github.com/0xProject/0x-api/commit/e050a226d42eaa0eb9f07ef994b2f59b56d8b46f))
### Features
* Curve BTC Bridge ([#294](https://github.com/0xProject/0x-api/issues/294)) ([82f9276](https://github.com/0xProject/0x-api/commit/82f9276bc70bdeda4c8564952e9f6e675bc88021))
* Depth Chart API endpoint ([#290](https://github.com/0xProject/0x-api/issues/290)) ([540f92e](https://github.com/0xProject/0x-api/commit/540f92ea81bf48921bea4ddf00336d9e3abf9e99))
* Graceful Shutdowns ([#280](https://github.com/0xProject/0x-api/issues/280)) ([5736c74](https://github.com/0xProject/0x-api/commit/5736c74ca537f5a469012a61da3d4da77e281cd7))
* Update protocol fee to 70k. Yield to event loop in path optimizer ([#285](https://github.com/0xProject/0x-api/issues/285)) ([a996c7b](https://github.com/0xProject/0x-api/commit/a996c7b284ac3f3e56d12861702397a3ed9ea17e))
# [1.11.0](https://github.com/0xProject/0x-api/compare/v1.10.2...v1.11.0) (2020-07-20)
### Features
* support ExchangeProxy for swap quotes (Swap V1) ([#262](https://github.com/0xProject/0x-api/issues/262)) ([3415d63](https://github.com/0xProject/0x-api/commit/3415d636df4591188d7a46b10597e511e4659003)), closes [#268](https://github.com/0xProject/0x-api/issues/268) [#269](https://github.com/0xProject/0x-api/issues/269) [#282](https://github.com/0xProject/0x-api/issues/282)
* Updates BZRX token address to new BZRX token ([#279](https://github.com/0xProject/0x-api/issues/279)) ([8032d96](https://github.com/0xProject/0x-api/commit/8032d96173e17f1f050741efb1f63a1edac2d9e5))
## [1.10.2](https://github.com/0xProject/0x-api/compare/v1.10.1...v1.10.2) (2020-07-13)
### Bug Fixes
* Changes latency to be capped at 600ms, added some extra typing ([#275](https://github.com/0xProject/0x-api/issues/275)) ([cd545c8](https://github.com/0xProject/0x-api/commit/cd545c8200a9abe6d53a894a76b89ab40220c327))
## [1.10.1](https://github.com/0xProject/0x-api/compare/v1.10.0...v1.10.1) (2020-07-06)
### Bug Fixes
* egs polling and forwarder buy ([#274](https://github.com/0xProject/0x-api/issues/274)) ([94587d0](https://github.com/0xProject/0x-api/commit/94587d0cee1d7693d616a88e1e4b8b8f895e84fe))
* specify ethgasstation url ([#273](https://github.com/0xProject/0x-api/issues/273)) ([16eabc0](https://github.com/0xProject/0x-api/commit/16eabc090e15c694861b1ef20f9a9a60a5b3f1b8))
* Update to ethgasstation proxy endpoint ([#272](https://github.com/0xProject/0x-api/issues/272)) ([6d0874f](https://github.com/0xProject/0x-api/commit/6d0874ff1410a310f5534b3730f98c208ccfd5dc))
# [1.10.0](https://github.com/0xProject/0x-api/compare/v1.9.0...v1.10.0) (2020-06-29)
### Bug Fixes
* meta txn limiting - synchronize db ([#270](https://github.com/0xProject/0x-api/issues/270)) ([a3448ab](https://github.com/0xProject/0x-api/commit/a3448ab002072d39f50bc33b5970b431b24ba813))
* remove NonceTrackerSubprovider ([#271](https://github.com/0xProject/0x-api/issues/271)) ([7b8aa6e](https://github.com/0xProject/0x-api/commit/7b8aa6e9c09443d951bf92db5342af5186586caf))
### Features
* Add UMA token to 0x API metadata registry ([#266](https://github.com/0xProject/0x-api/issues/266)) ([a057aee](https://github.com/0xProject/0x-api/commit/a057aeec257a738896288ea6c665d96eba5f9d0a))
# [1.9.0](https://github.com/0xProject/0x-api/compare/v1.8.0...v1.9.0) (2020-06-22)
### Bug Fixes
* asset swapper monorepo f14b6f2ba ([#257](https://github.com/0xProject/0x-api/issues/257)) ([a03630a](https://github.com/0xProject/0x-api/commit/a03630a918c98918ce114a7b6bc5a73ce197da94))
* Disable quote validation temporarily ([#259](https://github.com/0xProject/0x-api/issues/259)) ([6064f3e](https://github.com/0xProject/0x-api/commit/6064f3e58054da3ab0b44bac0b7d24d1cf672497))
* filter tokens in prices which do not exist on the network ([#265](https://github.com/0xProject/0x-api/issues/265)) ([864ea92](https://github.com/0xProject/0x-api/commit/864ea9203af8f30890f851bd32ffde199712504d))
* Fix parameters sent off to RFQT providers to be unescaped ([#264](https://github.com/0xProject/0x-api/issues/264)) ([939cae1](https://github.com/0xProject/0x-api/commit/939cae1ca9a35d762c05be79a27de45870b9079e))
* validation gas limit ([#260](https://github.com/0xProject/0x-api/issues/260)) ([f50425c](https://github.com/0xProject/0x-api/commit/f50425c977723f7402b80dec2cb24ffcd23b969e)), closes [#259](https://github.com/0xProject/0x-api/issues/259)
* WETH wrap gas estimate ([#256](https://github.com/0xProject/0x-api/issues/256)) ([f07b4a8](https://github.com/0xProject/0x-api/commit/f07b4a810edb22002266a126b1e2128e25be1323))
### Features
* add signer liveness status gauge ([#255](https://github.com/0xProject/0x-api/issues/255)) ([11446e7](https://github.com/0xProject/0x-api/commit/11446e786a5ca62d0d12b1303bdf97827e3daf06))
* support renamed parameters in RFQT maker endpoint ([#258](https://github.com/0xProject/0x-api/issues/258)) ([d83bbb1](https://github.com/0xProject/0x-api/commit/d83bbb1ee01ca8885d0a95db6ee08ca4d9b25bf4))
# [1.8.0](https://github.com/0xProject/0x-api/compare/v1.7.0...v1.8.0) (2020-06-15)
### Bug Fixes
* uniswap v2 buys ([#253](https://github.com/0xProject/0x-api/issues/253)) ([225db22](https://github.com/0xProject/0x-api/commit/225db22b71f36344f71c53178c41eb1648b1cbba))
### Features
* Add support for UniswapV2 and MultiBridge ([#250](https://github.com/0xProject/0x-api/issues/250)) ([29636e7](https://github.com/0xProject/0x-api/commit/29636e7a2d69e2077ee1a496b99767e02239f737)), closes [#252](https://github.com/0xProject/0x-api/issues/252)
* add transaction price information to meta-txn endpoints for UI ([#248](https://github.com/0xProject/0x-api/issues/248)) ([d26d6c6](https://github.com/0xProject/0x-api/commit/d26d6c6958798dddbb889cdf74e01285eacbd7f6))
# [1.7.0](https://github.com/0xProject/0x-api/compare/v1.6.0...v1.7.0) (2020-06-08)
### Bug Fixes
* Add RFQT configs when instantiating SwapQuoter ([#218](https://github.com/0xProject/0x-api/issues/218)) ([28e1214](https://github.com/0xProject/0x-api/commit/28e1214186c3285ca61de9bd7ca38ec84f9cbf32))
### Features
* added rewards per ZRX to pools endpoint ([#235](https://github.com/0xProject/0x-api/issues/235)) ([d8b8941](https://github.com/0xProject/0x-api/commit/d8b8941f9492720254b6a8d7c491ad16a3deedd4))
* return ethereum transaction status in the response from metatxn status ([#247](https://github.com/0xProject/0x-api/issues/247)) ([ce973a2](https://github.com/0xProject/0x-api/commit/ce973a2cac07fd9dcf783f284f3db125c242317f))
# [1.6.0](https://github.com/0xProject/0x-api/compare/v1.5.0...v1.6.0) (2020-06-01)
### Bug Fixes
* Re-pin asset-swapper to latest monorepo commit ([#240](https://github.com/0xProject/0x-api/issues/240)) ([dfbf0e7](https://github.com/0xProject/0x-api/commit/dfbf0e719fe1a2371e0a2e5dcc9a2824f9de4529))
### Features
* add estimatedGas, estimatedGasTokenRefund and minimumProtocolFee to /quote and /price response ([#237](https://github.com/0xProject/0x-api/issues/237)) ([f7f3277](https://github.com/0xProject/0x-api/commit/f7f3277510fcc05f03f0a187e64364e02305e3ee))
# [1.5.0](https://github.com/0xProject/0x-api/compare/v1.4.0...v1.5.0) (2020-05-25)
### Bug Fixes
* unify the response data from /swap/v0/price and /meta_transaction/v0/price ([#228](https://github.com/0xProject/0x-api/issues/228)) ([62f3fae](https://github.com/0xProject/0x-api/commit/62f3fae42a8ab7e73b644f0e8c523d655e6319e8))
### Features
* Add Prometheus Monitoring ([#222](https://github.com/0xProject/0x-api/issues/222)) ([5a51add](https://github.com/0xProject/0x-api/commit/5a51add4f0351d1e8567817411cdd24a984c2c28))
* added an epochs/n endpoint to get info on an arbitrary epoch ([#230](https://github.com/0xProject/0x-api/issues/230)) ([68ec159](https://github.com/0xProject/0x-api/commit/68ec1595b99c15e50c5cfeae682cf16b68d83be1))
* lower default slippage percentage to 1% ([#238](https://github.com/0xProject/0x-api/issues/238)) ([c7ec0ff](https://github.com/0xProject/0x-api/commit/c7ec0ff80d13d141dbc062b88ef5a97fd5b387a3))
* MetaTxn add signer heartbeat and status ([#236](https://github.com/0xProject/0x-api/issues/236)) ([3a11867](https://github.com/0xProject/0x-api/commit/3a118670ec6376203400650f39b93e06fc1c76af))
* set default skip RFQt buy requests to false ([#232](https://github.com/0xProject/0x-api/issues/232)) ([a5d7a1c](https://github.com/0xProject/0x-api/commit/a5d7a1ce8d539382fada237f77fbcb637aaf3791))
# [1.4.0](https://github.com/0xProject/0x-api/compare/v1.3.0...v1.4.0) (2020-05-18)
### Bug Fixes
- re-allow unknown tokens to be queried in the swap/quote endpoint ([#226](https://github.com/0xProject/0x-api/issues/226)) ([1379e63](https://github.com/0xProject/0x-api/commit/1379e638693e030ab343adfa9893a6f42081ea01))
### Features
- add transaction watcher service ([#215](https://github.com/0xProject/0x-api/issues/215)) ([7bbb9c6](https://github.com/0xProject/0x-api/commit/7bbb9c6f0992ae2a9a9ae9f2fe6d59f99a8e121a))
- Improve RFQ-T logging ([#219](https://github.com/0xProject/0x-api/issues/219)) ([22b6b0c](https://github.com/0xProject/0x-api/commit/22b6b0c1fb15513bc1225ca5f3a8918639fe8f32))
# [1.3.0](https://github.com/0xProject/0x-api/compare/v1.2.0...v1.3.0) (2020-05-11)
### Features
- Remove Kyber exclusion ([#211](https://github.com/0xProject/0x-api/issues/211)) ([aa600ab](https://github.com/0xProject/0x-api/commit/aa600abd74bb963303720d8a3cdf3b5f5044ae4f))
- RFQ-T follow-ups ([#201](https://github.com/0xProject/0x-api/issues/201)) ([a55f22e](https://github.com/0xProject/0x-api/commit/a55f22ee866f0ce8a8e5164829bc511838019a47)), closes [/github.com/0xProject/0x-api/pull/201#discussion_r417775612](https://github.com//github.com/0xProject/0x-api/pull/201/issues/discussion_r417775612)
# [1.2.0](https://github.com/0xProject/0x-api/compare/v1.1.0...v1.2.0) (2020-05-04)
### Bug Fixes
- specify an image name for the Docker push, fix typos ([512deab](https://github.com/0xProject/0x-api/commit/512deab78da744e86ca7e2f58d2c6e09e4f78c05))
### Features
- Added pool breakdown of operator vs member stake, added option to add… ([#202](https://github.com/0xProject/0x-api/issues/202)) ([e20daa5](https://github.com/0xProject/0x-api/commit/e20daa5bb1cf6271b83d977459227fd80d1794cd))
# [1.1.0](https://github.com/0xProject/0x-api/compare/v1.0.0...v1.1.0) (2020-05-01)
### Bug Fixes
- sixtySecondsFromNow timestamp ([#208](https://github.com/0xProject/0x-api/issues/208)) ([f82785d](https://github.com/0xProject/0x-api/commit/f82785ddd9ee465ae70907d443e79df3369e093c))
- Typo in semantic release configuration ([d4030a0](https://github.com/0xProject/0x-api/commit/d4030a085d7f6847504087e97608c93c7031b57e))
### Features
- Support RFQT via Meta-txn Endpoints ([#203](https://github.com/0xProject/0x-api/issues/203)) ([ea9a7e0](https://github.com/0xProject/0x-api/commit/ea9a7e0ad32855796c4cf9ef125b075f5761e71d))

29
apps-node/api/Dockerfile Normal file
View File

@ -0,0 +1,29 @@
# Stage 1
FROM node:18-alpine as yarn-install
WORKDIR /usr/src/app
# Install app dependencies
COPY package.json yarn.lock ./
RUN apk update && \
apk upgrade && \
apk add --no-cache --virtual build-dependencies bash git openssh python3 make g++ libc6-compat && \
yarn --frozen-lockfile --no-cache && \
apk del build-dependencies && \
yarn cache clean
# Runtime container with minimal dependencies
FROM node:18-alpine
RUN apk update && \
apk upgrade && \
apk add ca-certificates libc6-compat && \
ln -s /lib/libc.musl-x86_64.so.1 /lib/ld-linux-x86-64.so.2
WORKDIR /usr/src/app
COPY --from=yarn-install /usr/src/app/node_modules /usr/src/app/node_modules
# Bundle app source
COPY . .
RUN yarn build
EXPOSE 3000
CMD echo "use docker CLI to specify a startup command"

View File

@ -1,4 +1,5 @@
Copyright 2020 ZeroEx Labs
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

216
apps-node/api/README.md Normal file
View File

@ -0,0 +1,216 @@
[![Version](https://img.shields.io/github/package-json/v/0xProject/0x-api)](https://github.com/0xProject/0x-api/releases)
[![Docs](https://img.shields.io/badge/docs-website-yellow.svg)](https://0x.org/docs/api)
[![Chat with us on Discord](https://img.shields.io/badge/chat-Discord-blueViolet.svg)](https://discord.com/invite/d3FTX3M)
[![Twitter](https://img.shields.io/badge/follow-twitter-brightgreen)](https://twitter.com/0xProject)
[![Continuous Integration](https://github.com/0xProject/0x-api/workflows/Build%20and%20Test/badge.svg)](https://github.com/0xProject/0x-api/actions?query=workflow%3A%22Build+and+Test%22+branch%3Amaster)
![alt text](https://raw.githubusercontent.com/0xProject/0x-api/master/0x-api.png '0x API')
## Table of contents
- [Introduction](#introduction)
- [Services](#services)
- [HTTP Services](#http-services)
- [Data Services](#data-services)
- [Getting started](#getting-started)
- [Pre-requirements](#pre-requirements)
- [Developing](#developing)
- [Commands](#commands)
- [Database](#database)
- [Deployment](#deployment)
- [Release](#release)
- [Legal Disclaimer](#legal-disclaimer)
## Introduction
The 0x API is a collection of services and endpoints that can be run together or separately. In aggregate, the APIs provide interfaces to 0x liquidity.
Everything can be run monolithically via `yarn start` and `docker-compose` up as described in [Getting Started](#getting-started).
## Services
The API contains different services that serve a collection of HTTP or websocket endpoints and keep your order states in sync with the Ethereum state.
### HTTP Services
These are services that handle HTTP requests and responses.
| Name | Path | Run Command | Requires Ethereum JSON RPC Provider? | Requires Relational Database? |
| ---------------------------------------------- | ------------------- | ------------------------------------------ | ------------------------------------ | ----------------------------- |
| All HTTP Services | `/*` | `yarn start:service:http` | Yes | Yes |
| [Swap](https://0x.org/docs/api#swap) | `/swap` | `yarn start:service:swap_http` | Yes | Yes |
| [Orderbook](https://0x.org/docs/api#orderbook) | `/orderbook` | `yarn start:service:sra_http` | No | Yes |
| Meta Transaction Service | `/meta_transaction` | `yarn start:service:meta_transaction_http` | Yes | Yes |
### Data Services
The transaction watcher ensures that the data being served is present and up-to-date by keeping the database in sync with Ethereum. The endpoints above run without it, but would be providing degraded or non-functional service.
| Name | Run Command | Requires Ethereum JSON RPC Provider? | Requires Relational Database? |
| ------------------------------------------------------------- | ---------------------------------------- | ------------------------------------ | ----------------------------- |
| Transaction Watcher (monitor and broadcast meta transactions) | `yarn start:service:transaction_watcher` | Yes | Yes |
## Getting started
#### Pre-requirements
- [Node.js](https://nodejs.org/en/download/) > v16.x
- [Yarn](https://yarnpkg.com/en/) > v1.x
- [Docker](https://www.docker.com/products/docker-desktop) > 19.x
#### Developing
To get a local development version of `0x-api` running:
1. Clone the repo.
2. Create an `.env` file and copy the content from the `.env_example` file. Defaults are defined in `config.ts`/`config.js`. The bash environment takes precedence over the `.env` file. If you run `source .env`, changes to the `.env` file will have no effect until you unset the colliding variables.
| Environment Variable | Default | Description |
| -------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CHAIN_ID` | Required. No default. | The chain id you'd like your API to run on (e.g: `1` -> mainnet, `137` -> Polygon). Defaults to `42` in the API, but required for `docker-compose up`. |
| `ETHEREUM_RPC_URL` | Required. No default. | The URL used to issue JSON RPC requests. Use `http://localhost:8545` to use the local ganache instance. |
| `ZERO_EX_GAS_API_URL` | Optional. Default: `'https://gas.api.0x.org/source/median'` | The URL used to fetch gas prices. It should be changed to a matching endpoint based on `CHAIN_ID`. |
| `POSTGRES_URI` | Required. Default for dev: `postgresql://api:api@localhost/api` | A URI of a running postgres instance. By default, the API will create all necessary tables. A default instance is spun up in `docker-compose up` |
| `POSTGRES_READ_REPLICA_URIS` | Optional. No default | A comma separated list of URIs of running postgres read replica instances. |
| `FEE_RECIPIENT_ADDRESS` | `0x0000000000000000000000000000000000000000` | The Ethereum address which should be specified as the fee recipient in orders your API accepts. |
| `TAKER_FEE_ASSET_DATA` | `0x` | The taker fee token asset data for created 0x orders. |
| `TAKER_FEE_UNIT_AMOUNT` | `0` | The flat taker fee amount you'd like to receive for filled orders hosted by you. |
| `WHITELIST_ALL_TOKENS` | `false` | A boolean determining whether all tokens should be allowed to be posted. |
| `SWAP_IGNORED_ADDRESSES` | `[]` | A comma separated list of addresses to ignore. These addresses are persisted but not used in any `/swap/*` endpoints |
| `META_TXN_SUBMIT_WHITELISTED_API_KEYS` | `[]` | A comma separated list of whitelisted 0x API keys that can use the meta-txn /submit endpoint. |
| `META_TXN_RELAY_PRIVATE_KEYS` | `[]` | A comma separated list of meta-txn relay sender private keys managed by the TransactionWatcherSignerService. |
| `META_TXN_SIGNING_ENABLED` | `true` | A boolean determining whether the meta-txn signs and submits transactions . |
| `META_TXN_MAX_GAS_PRICE_GWEI` | `50` | The maximum gas price (in gwei) the meta-txn service will submit a transaction at. If the gas price of the network exceeds this value then the meta-txn service will be disabled. |
| `META_TXN_RELAY_EXPECTED_MINED_SEC` | Default: `120` | The expected time for a meta-txn to be included in a block. |
| `ENABLE_PROMETHEUS_METRICS` | Default: `false` | A boolean determining whether to enable prometheus monitoring. |
| `PROMETHEUS_PORT` | Default: `8080` | The port from which prometheus metrics should be served. |
| `KAFKA_BROKERS` | Optional. No default. | A comma separated list of Kafka broker servers |
| `KAFKA_TOPIC_QUOTE_REPORT` | Optional. No default | The name of the Kafka topic to publish quote reports on. Setting this and `KAFKA_BROKERS` enable publishing. |
3. Install the dependencies:
```sh
yarn
```
4. Build the project:
```sh
yarn build
```
5. Run `docker-compose up` to run the other dependencies required for the API. This uses the local `docker-compose.yml` file.
6. Run the database migrations:
```
yarn db:migrate
```
7. Start the API
```sh
yarn start
```
For development:
```sh
yarn dev
```
#### Adding a new contract
NOTE: This repo is undergoing a tooling change.
To add a new contract, you will need to add it to `compiler.json`.
You can generate the entire list by running the following:
```sh
find . -type f -name "*.sol" | grep -v foundry | grep -v "contracts/lib" | grep -v "node_modules"
```
#### Developing on Ganache
To use ganache, use the `.env` file below:
```
CHAIN_ID=1337
ETHEREUM_RPC_URL=http://localhost:8545
```
Then run
```
$ docker-compose up
$ yarn dev
```
#### Developing on Foundry
Install Foundry if needed.
Initialize git submodule:
```sh
git submodule update --init --recursive
```
Compile using `forge`
```
cd contracts
forge build
```
## Testing
Run `yarn test`. It's really that easy :)
Tip: Test suites set up and teardown sandboxed environments, so using `.only` on `it` and `describe` statements will save lots of development time.
## Commands
- `yarn build` - Build the code
- `yarn test` - Test the code
- `yarn lint` - Lint the code
- `yarn start` - Starts the API
- `yarn dev` - Starts the API in dev-mode
- `yarn watch` - Watch the source code and rebuild on change
- `yarn prettier` - Auto-format the code
- `yarn release` - Release a new version of the 0x-api
## Deployment
A Docker image is built and hosted by [Dockerhub](https://hub.docker.com/r/0xorg/0x-api) every time a change to the `master` branch occurs. A Docker image is built, pushed and [tagged](https://hub.docker.com/r/0xorg/0x-api/tags) when a new version is [released](https://github.com/0xProject/0x-api/releases) as well.
Running this image will run 0x API as a monolith, with all its dependencies. You can run any of the [services](#services) separately by [overriding the Docker command](https://docs.docker.com/engine/reference/run/#cmd-default-command-or-options) with the service-specific command when running the container.
When versioning the API, we freeze the old version in a separate branch so that we can deploy patches, and continue to support the old version until it's officially deprecated. **Be aware when contributing fixes that you may want to apply the fix to an older version too.**
This is a list of endpoints supported by different versions of the API. If an endpoint is not on this list, it is by default pointed to the master branch.
| Endpoint(s) | API version branch | @0x/asset-swapper branch |
| ----------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------- |
| `/swap/v0/`, `/meta_transaction/v0` | https://github.com/0xProject/0x-api/tree/freeze/swap-v0 | https://github.com/0xProject/0x-monorepo/tree/v0-asset-swapper |
| `/sra/v3` | https://github.com/0xProject/0x-api/tree/freeze/sra-v3 | N/A |
## Release
Releases are triggered automatically by the [release GitHub action](https://github.com/0xProject/0x-api/actions?query=workflow%3ARelease).
They can also be triggered manually by using `yarn release`, which requires a [GITHUB_TOKEN](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) environment variable.
## Database
This project uses [TypeORM](https://github.com/typeorm/typeorm). It makes it easier for anyone to switch out the backing database used by this project. By default, this project uses a [PostgreSQL](https://www.postgresql.org/) database.
To add a migration, you may use the following command:
```
yarn db:migration:create -n myMigration
```
## Legal Disclaimer
The laws and regulations applicable to the use and exchange of digital assets and blockchain-native tokens, including through any software developed using the licensed work created by ZeroEx Intl. as described here (the “Work”), vary by jurisdiction. As set forth in the Apache License, Version 2.0 applicable to the Work, developers are “solely responsible for determining the appropriateness of using or redistributing the Work,” which includes responsibility for ensuring compliance with any such applicable laws and regulations.
See the Apache License, Version 2.0 for the specific language governing all applicable permissions and limitations: http://www.apache.org/licenses/LICENSE-2.0

View File

@ -0,0 +1,76 @@
{
"artifactsDir": "./test/generated-artifacts",
"contractsDir": "./contracts",
"contracts": [
"./contracts/src/AaveSampler.sol",
"./contracts/src/AlgebraMultiQuoter.sol",
"./contracts/src/AlgebraSampler.sol",
"./contracts/src/ApproximateBuys.sol",
"./contracts/src/BalanceChecker.sol",
"./contracts/src/BalancerSampler.sol",
"./contracts/src/BalancerV2BatchSampler.sol",
"./contracts/src/BalancerV2Common.sol",
"./contracts/src/BancorSampler.sol",
"./contracts/src/BancorV3Sampler.sol",
"./contracts/src/CompoundSampler.sol",
"./contracts/src/CurveSampler.sol",
"./contracts/src/DODOSampler.sol",
"./contracts/src/DODOV2Sampler.sol",
"./contracts/src/ERC20BridgeSampler.sol",
"./contracts/src/FakeTaker.sol",
"./contracts/src/GMXSampler.sol",
"./contracts/src/KyberDmmSampler.sol",
"./contracts/src/KyberElasticMultiQuoter.sol",
"./contracts/src/KyberElasticSampler.sol",
"./contracts/src/LidoSampler.sol",
"./contracts/src/MStableSampler.sol",
"./contracts/src/MakerPSMSampler.sol",
"./contracts/src/MooniswapSampler.sol",
"./contracts/src/NativeOrderSampler.sol",
"./contracts/src/PlatypusSampler.sol",
"./contracts/src/SamplerUtils.sol",
"./contracts/src/ShellSampler.sol",
"./contracts/src/SynthetixSampler.sol",
"./contracts/src/TraderJoeV2MultiQuoter.sol",
"./contracts/src/TwoHopSampler.sol",
"./contracts/src/UniswapSampler.sol",
"./contracts/src/UniswapV2Sampler.sol",
"./contracts/src/UniswapV3MultiQuoter.sol",
"./contracts/src/UniswapV3Sampler.sol",
"./contracts/src/UtilitySampler.sol",
"./contracts/src/VelodromeSampler.sol",
"./contracts/src/WooPPSampler.sol",
"./contracts/src/interfaces/IBalancer.sol",
"./contracts/src/interfaces/IBalancerV2Vault.sol",
"./contracts/src/interfaces/IBancor.sol",
"./contracts/src/interfaces/IBancorV3.sol",
"./contracts/src/interfaces/ICurve.sol",
"./contracts/src/interfaces/IGMX.sol",
"./contracts/src/interfaces/IMStable.sol",
"./contracts/src/interfaces/IMooniswap.sol",
"./contracts/src/interfaces/IPlatypus.sol",
"./contracts/src/interfaces/IShell.sol",
"./contracts/src/interfaces/IUniswapExchangeQuotes.sol",
"./contracts/src/interfaces/IUniswapV2Router01.sol",
"./contracts/test/TestNativeOrderSampler.sol"
],
"useDockerisedSolc": false,
"isOfflineMode": false,
"shouldSaveStandardInput": true,
"compilerSettings": {
"evmVersion": "istanbul",
"optimizer": { "enabled": true, "runs": 200, "details": { "yul": false, "deduplicate": true } },
"outputSelection": {
"*": {
"*": [
"abi",
"devdoc",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
}
}
}

View File

@ -0,0 +1,22 @@
[profile.default]
src = 'src'
out = 'foundry-artifacts'
test = 'test'
path_pattern = '*.t.sol'
libs = ["lib"]
remappings = [
'@0x/contracts-utils/=../../../node_modules/@0x/contracts-utils/',
'@0x/contracts-erc20/=../../../node_modules/@0x/contracts-erc20/',
'@0x/contracts-zero-ex/=../../../node_modules/@0x/contracts-zero-ex/',
'@uniswap/v3-core/=../../../node_modules/@uniswap/v3-core/',
'@uniswap/v3-periphery/=../../../node_modules/@uniswap/v3-periphery/',
'@kybernetwork/=../../../node_modules/@kybernetwork/',
'@cryptoalgebra/core/=../../../node_modules/@cryptoalgebra/core/',
'@cryptoalgebra/periphery/=../../../node_modules/@cryptoalgebra/periphery/',
'@traderjoe-xyz/joe-v2/=../../../node_modules/@traderjoe-xyz/joe-v2/',
'openzeppelin/=../../../node_modules/openzeppelin'
]
allow_paths = ["../../../node_modules"]
cache_path = 'foundry-cache'
optimizer_runs = 1000000
fs_permissions = [{ access = "read-write", path = "./"}]

@ -0,0 +1 @@
Subproject commit c2236853aadb8e2d9909bbecdc490099519b70a4

View File

@ -0,0 +1,238 @@
pragma solidity >=0.6.5;
pragma experimental ABIEncoderV2;
import "forge-std/Script.sol";
import "../src/interfaces/IUniswapV3.sol";
import "../src/UniswapV3MultiQuoter.sol";
import "../src/UniswapV3Common.sol";
/// @title Writes UniswapV3 gas estimates to file
/// @notice Gas estimates for UniswapV3QuoterV2 and UniswapV3MultiQuoter are written with each quote function
/// being called from a clean slate (no warm storage reads). UniswapV3QuoterV2 reverts at the end of the quote
/// function while UniswapV3MultiQuoter doesn't. Hence, after very call to UniswapV3MultiQuoter's quote function,
/// the data writer's functions revert.
contract UniswapV3GasDataWriter is Script, UniswapV3Common {
string private filePath;
IUniswapV3QuoterV2 private immutable uniQuoter;
IUniswapV3MultiQuoter private immutable multiQuoter;
IUniswapV3Factory private immutable factory;
string private constant headers = "path,amount,isInput,ticksCrossed,mqGasEstimate,uqGasEstimate";
constructor(
string memory filePath_,
IUniswapV3QuoterV2 uniQuoter_,
IUniswapV3MultiQuoter multiQuoter_,
IUniswapV3Factory factory_
) {
filePath = filePath_;
uniQuoter = uniQuoter_;
multiQuoter = multiQuoter_;
factory = factory_;
// write the CSV file headers
vm.writeLine(filePath, headers);
}
/// @notice Writes exact input gas estimates for given UniswapV3 path and set of amounts.
/// @param uniswapPath The path of the swap, i.e. each token pair and the pool fee
/// @param amounts The amounts in of the first token to swap
function writeGasDataForExactInputs(bytes memory uniswapPath, uint256[] memory amounts) external {
uint32[] memory uqTicksCrossed = new uint32[](amounts.length);
uint256[] memory uqInputGasEst = new uint256[](amounts.length);
for (uint256 i = 0; i < amounts.length; ++i) {
try uniQuoter.quoteExactInput{gas: 700e3}(uniswapPath, amounts[i]) returns (
uint256,
uint160[] memory,
uint32[] memory ticksCrossed,
uint256 gasEstimate
) {
uqTicksCrossed[i] = ticksCrossed[0];
uqInputGasEst[i] = gasEstimate;
} catch {}
}
uint256[] memory mqInputGasEst;
try multiQuoter.quoteExactMultiInput(factory, uniswapPath, amounts) {} catch (bytes memory reason) {
(, , mqInputGasEst) = catchMultiSwapResult(reason);
}
for (uint256 i = 0; i < amounts.length; ++i) {
if (mqInputGasEst[i] == 0 || uqInputGasEst[i] == 0) {
continue;
}
string memory pathStr = toHexString(uniswapPath);
string memory amountStr = toHexString(abi.encodePacked(amounts[i]));
string memory ticksCrossedStr = toHexString(abi.encodePacked(uqTicksCrossed[i]));
string memory mqGasEstimateStr = toHexString(abi.encodePacked(mqInputGasEst[i]));
string memory uqGasEstimateStr = toHexString(abi.encodePacked(uqInputGasEst[i]));
string memory row = string(
abi.encodePacked(
pathStr,
",",
amountStr,
",",
"TRUE",
",",
ticksCrossedStr,
",",
mqGasEstimateStr,
",",
uqGasEstimateStr
)
);
vm.writeLine(filePath, row);
}
revert(); // revert to clear warm storage from multiquoter
}
/// @notice Writes exact output gas estimates for given UniswapV3 path and set of amounts.
/// @param uniswapPath The path of the swap, i.e. each token pair and the pool fee
/// @param amounts The amounts out of the first token to swap
function writeGasDataForExactOutputs(bytes memory uniswapPath, uint256[] memory amounts) external {
uint32[] memory uqTicksCrossed = new uint32[](amounts.length);
uint256[] memory uqOutputGasEst = new uint256[](amounts.length);
for (uint256 i = 0; i < amounts.length; ++i) {
try uniQuoter.quoteExactOutput{gas: 700e3}(uniswapPath, amounts[i]) returns (
uint256,
uint160[] memory,
uint32[] memory ticksCrossed,
uint256 gasEstimate
) {
uqTicksCrossed[i] = ticksCrossed[0];
uqOutputGasEst[i] = gasEstimate;
} catch {}
}
uint256[] memory mqOutputGasEst;
try multiQuoter.quoteExactMultiOutput(factory, uniswapPath, amounts) {} catch (bytes memory reason) {
(, , mqOutputGasEst) = catchMultiSwapResult(reason);
}
for (uint256 i = 0; i < amounts.length; ++i) {
if (mqOutputGasEst[i] == 0 || uqOutputGasEst[i] == 0) {
continue;
}
string memory pathStr = toHexString(uniswapPath);
string memory amountStr = toHexString(abi.encodePacked(amounts[i]));
string memory ticksCrossedStr = toHexString(abi.encodePacked(uqTicksCrossed[i]));
string memory mqGasEstimateStr = toHexString(abi.encodePacked(mqOutputGasEst[i]));
string memory uqGasEstimateStr = toHexString(abi.encodePacked(uqOutputGasEst[i]));
string memory row = string(
abi.encodePacked(
pathStr,
",",
amountStr,
",",
"FALSE",
",",
ticksCrossedStr,
",",
mqGasEstimateStr,
",",
uqGasEstimateStr
)
);
vm.writeLine(filePath, row);
}
revert(); // revert to clear warm storage from multiquoter
}
function toHexString(bytes memory buffer) private pure returns (string memory) {
// Fixed buffer size for hexadecimal convertion
bytes memory converted = new bytes(buffer.length * 2);
bytes memory _base = "0123456789abcdef";
for (uint256 i = 0; i < buffer.length; i++) {
converted[i * 2] = _base[uint8(buffer[i]) / _base.length];
converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length];
}
return string(abi.encodePacked("0x", converted));
}
}
/// @title Iterates through multiple token paths and pools and writes gas estimate data to file.
/// @notice The generated CSV file should be used o perform a linear regression between
/// UniswapV3QuoterV2 gas results and UniswapV3MultiQuoter gas results. The results from those regression
/// should be used to populate gas scaling parameters for UniswapV3MultiQuoter.
contract UniswapV3GasDataCollector is Script, UniswapV3Common {
IUniswapV3QuoterV2 private constant uniQuoter = IUniswapV3QuoterV2(0x61fFE014bA17989E743c5F6cB21bF9697530B21e);
IUniswapV3Factory private factory;
UniswapV3MultiQuoter private multiQuoter;
string private constant filePath = "UniswapV3GasData.csv";
function setUp() public {
factory = uniQuoter.factory();
multiQuoter = new UniswapV3MultiQuoter();
}
function run() public {
UniswapV3GasDataWriter w = new UniswapV3GasDataWriter(filePath, uniQuoter, multiQuoter, factory);
address[12] memory tokenList;
tokenList[0] = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC
tokenList[1] = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH
tokenList[2] = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // USDT
tokenList[3] = 0x6B175474E89094C44Da98b954EedeAC495271d0F; // DAI
tokenList[4] = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; // WBTC
tokenList[5] = 0xD533a949740bb3306d119CC777fa900bA034cd52; // CRV
tokenList[6] = 0x111111111117dC0aa78b770fA6A738034120C302; // 1INCH
tokenList[7] = 0xE41d2489571d322189246DaFA5ebDe1F4699F498; // ZRX
tokenList[8] = 0x4d224452801ACEd8B2F0aebE155379bb5D594381; // APE
tokenList[9] = 0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919; // RAI
tokenList[10] = 0x3845badAde8e6dFF049820680d1F14bD3903a5d0; // SAND
tokenList[11] = 0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0; // MATIC
uint256[][] memory amountsList = new uint256[][](10);
for (uint256 i = 0; i < amountsList.length; ++i) {
amountsList[i] = new uint256[](10);
for (uint256 j = 0; j < amountsList[i].length; ++j) {
amountsList[i][j] = (j + 1) * 10 ** (2 * i + 4);
}
}
for (uint256 i = 0; i < tokenList.length; ++i) {
for (uint256 j = 0; j < tokenList.length; ++j) {
console.log("Processing i=%d, j=%d", i + 1, j + 1);
console.log("--- %d / %d", i * tokenList.length + j + 1, tokenList.length * tokenList.length);
if (i == j) {
continue;
}
IERC20TokenV06[] memory tokenPath = new IERC20TokenV06[](2);
tokenPath[0] = IERC20TokenV06(tokenList[i]);
tokenPath[1] = IERC20TokenV06(tokenList[j]);
for (uint256 k = 0; k < amountsList.length; ++k) {
IUniswapV3Pool[][] memory poolPaths = getPoolPaths(
factory,
multiQuoter,
tokenPath,
amountsList[k][amountsList[k].length - 1]
);
for (uint256 l = 0; l < poolPaths.length; ++l) {
if (!isValidPoolPath(poolPaths[l])) {
continue;
}
bytes memory uniswapPath = toUniswapPath(tokenPath, poolPaths[l]);
try w.writeGasDataForExactInputs(uniswapPath, amountsList[k]) {} catch {}
try w.writeGasDataForExactOutputs(uniswapPath, amountsList[k]) {} catch {}
}
}
}
}
}
}

View File

@ -0,0 +1,87 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
contract AaveSampler {
/// @dev Sample sell quotes from Aave V2 and Aave V3.
/// @param aToken address of the aToken.
/// @param underlyingToken address of the underlying collateral token.
/// @param takerToken address of the taker token (what to sell).
/// @param makerToken address of the maker token (what to buy).
/// @param takerTokenAmounts taker token buy amounts for each sample
/// @return makerTokenAmounts maker amounts bought at each taker token
/// amount.
function sampleSellsFromAave(
address aToken,
address underlyingToken,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public pure returns (uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
if (takerToken == underlyingToken && makerToken == aToken) {
return takerTokenAmounts;
}
// Aave V2/V3 balances sometimes have a rounding error causing
// 1 fewer wei from being outputted during unwraps
if (takerToken == aToken && makerToken == underlyingToken) {
for (uint256 i = 0; i < numSamples; i++) {
takerTokenAmounts[i] -= 1;
}
return takerTokenAmounts;
}
}
/// @dev Sample buy quotes from Aave V2 and Aave V3.
/// @param aToken address of the aToken.
/// @param underlyingToken address of the underlying collateral token.
/// @param takerToken address of the taker token (what to sell).
/// @param makerToken address of the maker token (what to buy).
/// @param makerTokenAmounts maker token sell amounts for each sample
/// @return takerTokenAmounts taker amounts bought at each maker token
/// amount.
function sampleBuysFromAave(
address aToken,
address underlyingToken,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public pure returns (uint256[] memory takerTokenAmounts) {
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
if (takerToken == underlyingToken && makerToken == aToken) {
return makerTokenAmounts;
}
// Aave V2 balances sometimes have a rounding error causing
// 1 fewer wei from being outputted during unwraps
if (takerToken == aToken && makerToken == underlyingToken) {
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] -= 1;
}
return makerTokenAmounts;
}
}
}

View File

@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity >=0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IAlgebra.sol";
import "./interfaces/IMultiQuoter.sol";
import "./TickBasedAMMCommon.sol";
contract AlgebraCommon is TickBasedAMMCommon {
function toAlgebraPath(address[] memory tokenPath) internal pure returns (bytes memory algebraPath) {
require(tokenPath.length >= 2, "AlgebraCommon/invalid path lengths");
// Algebra paths are tightly packed as
// [token0, token1, token2, ... ]
algebraPath = new bytes(tokenPath.length * 20);
uint256 o;
assembly {
o := add(algebraPath, 32)
}
for (uint256 i = 0; i < tokenPath.length; ++i) {
address token = tokenPath[i];
assembly {
mstore(o, shl(96, token))
o := add(o, 20)
}
}
}
function isValidTokenPath(address factory, address[] memory tokenPath) internal view returns (bool) {
for (uint256 i = 0; i < tokenPath.length - 1; ++i) {
address pool = IAlgebraFactory(factory).poolByPair(tokenPath[i], tokenPath[i + 1]);
if (pool == address(0)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,336 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.7;
pragma experimental ABIEncoderV2;
import "@cryptoalgebra/core/contracts/libraries/TickMath.sol";
import "@cryptoalgebra/core/contracts/libraries/PriceMovementMath.sol";
import "@cryptoalgebra/core/contracts/libraries/SafeCast.sol";
import "@cryptoalgebra/core/contracts/libraries/LowGasSafeMath.sol";
import "@cryptoalgebra/core/contracts/libraries/LiquidityMath.sol";
import "@cryptoalgebra/periphery/contracts/libraries/Path.sol";
import "./interfaces/IAlgebra.sol";
import "./MultiQuoter.sol";
contract AlgebraMultiQuoter is MultiQuoter {
using SafeCast for uint256;
using LowGasSafeMath for int256;
using Path for bytes;
// the top level state of the multiswap, the results are transient.
struct SwapState {
// the amount remaining to be swapped in/out of the input/output asset
int256 amountSpecifiedRemaining;
// the amount already swapped out/in of the output/input asset
int256 amountCalculated;
// current sqrt(price)
uint160 sqrtPriceX96;
// the tick associated with the current price
int24 tick;
// the current liquidity in range
uint128 liquidity;
// the current quote amount we are querying liquidity for
uint256 amountsIndex;
// the current aggregate gas estimate for multi swap
uint256 gasAggregate;
}
// the intermediate calculations for each tick and quote amount
struct StepComputations {
// the price at the beginning of the step
uint160 sqrtPriceStartX96;
// the next tick to swap to from the current tick in the swap direction
int24 tickNext;
// whether tickNext is initialized or not
bool initialized;
// sqrt(price) for the next tick (1/0)
uint160 sqrtPriceNextX96;
// how much is being swapped in in this step
uint256 amountIn;
// how much is being swapped out
uint256 amountOut;
// how much fee is being paid in
uint256 feeAmount;
// how much gas was left before the current step
uint256 gasBefore;
}
/// @inheritdoc MultiQuoter
function getFirstSwapDetails(
address factory,
bytes memory path,
bool isExactInput
) internal view override returns (address pool, bool zeroForOne) {
address tokenIn;
address tokenOut;
if (isExactInput) {
(tokenIn, tokenOut) = path.decodeFirstPool();
} else {
(tokenOut, tokenIn) = path.decodeFirstPool();
}
zeroForOne = tokenIn < tokenOut;
pool = IAlgebraFactory(factory).poolByPair(tokenIn, tokenOut);
}
/// @inheritdoc MultiQuoter
function multiswap(
address p,
bool zeroForOne,
int256[] memory amounts
) internal view override returns (MultiSwapResult memory result) {
result.gasEstimates = new uint256[](amounts.length);
result.amounts0 = new int256[](amounts.length);
result.amounts1 = new int256[](amounts.length);
uint160 sqrtPriceLimitX96 = zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
IAlgebraPool pool = IAlgebraPool(p);
(uint160 sqrtPriceX96Start, int24 tickStart, uint16 fee, , , , ) = pool.globalState();
int24 tickSpacing = pool.tickSpacing();
bool exactInput = amounts[0] > 0;
SwapState memory state = SwapState({
amountSpecifiedRemaining: amounts[0],
amountCalculated: 0,
sqrtPriceX96: sqrtPriceX96Start,
tick: tickStart,
liquidity: pool.liquidity(),
amountsIndex: 0,
gasAggregate: 0
});
// continue swapping as long as we haven't used the entire input/output and haven't reached the price limit
while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
StepComputations memory step;
step.gasBefore = gasleft();
step.sqrtPriceStartX96 = state.sqrtPriceX96;
(step.tickNext, step.initialized) = nextInitializedTickWithinOneWord(
pool,
state.tick,
tickSpacing,
zeroForOne
);
// ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds
if (step.tickNext < TickMath.MIN_TICK) {
step.tickNext = TickMath.MIN_TICK;
} else if (step.tickNext > TickMath.MAX_TICK) {
step.tickNext = TickMath.MAX_TICK;
}
// get the price for the next tick
step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext);
// compute values to swap to the target tick, price limit, or point where input/output amount is exhausted
(state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = PriceMovementMath
.movePriceTowardsTarget(
zeroForOne,
state.sqrtPriceX96,
(zeroForOne == (step.sqrtPriceNextX96 < sqrtPriceLimitX96))
? sqrtPriceLimitX96
: step.sqrtPriceNextX96,
state.liquidity,
state.amountSpecifiedRemaining,
fee
);
if (exactInput) {
state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256();
state.amountCalculated = state.amountCalculated.sub(step.amountOut.toInt256());
} else {
state.amountSpecifiedRemaining += step.amountOut.toInt256();
state.amountCalculated = state.amountCalculated.add((step.amountIn + step.feeAmount).toInt256());
}
// shift tick if we reached the next price
if (state.sqrtPriceX96 == step.sqrtPriceNextX96) {
// if the tick is initialized, run the tick transition
if (step.initialized) {
(, int128 liquidityNet, , , , , , ) = pool.ticks(step.tickNext);
// if we're moving leftward, we interpret liquidityNet as the opposite sign
// safe because liquidityNet cannot be type(int128).min
if (zeroForOne) liquidityNet = -liquidityNet;
state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet);
}
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
state.gasAggregate += step.gasBefore - gasleft();
} else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96);
}
if (state.amountSpecifiedRemaining == 0) {
(result.amounts0[state.amountsIndex], result.amounts1[state.amountsIndex]) = zeroForOne == exactInput
? (amounts[state.amountsIndex], state.amountCalculated)
: (state.amountCalculated, amounts[state.amountsIndex]);
if (state.sqrtPriceX96 != step.sqrtPriceNextX96) {
result.gasEstimates[state.amountsIndex] = state.gasAggregate + (step.gasBefore - gasleft());
} else {
// we are moving to the next tick
result.gasEstimates[state.amountsIndex] = state.gasAggregate;
}
if (state.amountsIndex == amounts.length - 1) {
return (result);
}
state.amountsIndex += 1;
state.amountSpecifiedRemaining = amounts[state.amountsIndex].sub(amounts[state.amountsIndex - 1]);
}
}
for (uint256 i = state.amountsIndex; i < amounts.length; ++i) {
(result.amounts0[i], result.amounts1[i]) = zeroForOne == exactInput
? (amounts[i] - state.amountSpecifiedRemaining, state.amountCalculated)
: (state.amountCalculated, amounts[i] - state.amountSpecifiedRemaining);
result.gasEstimates[i] = state.gasAggregate;
}
}
/// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either
/// to the left (less than or equal to) or right (greater than) of the given tick
/// @param pool The Algebra pool to get next tick for
/// @param tick The starting tick
/// @param tickSpacing The spacing between usable ticks
/// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick)
/// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick
/// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks
function nextInitializedTickWithinOneWord(
IAlgebraPool pool,
int24 tick,
int24 tickSpacing,
bool lte
) private view returns (int24 next, bool initialized) {
{
// compress and round towards negative infinity if negative
assembly {
tick := sub(sdiv(tick, tickSpacing), and(slt(tick, 0), not(iszero(smod(tick, tickSpacing)))))
}
}
if (lte) {
// unpacking not made into a separate function for gas and contract size savings
int16 rowNumber;
uint8 bitNumber;
assembly {
bitNumber := and(tick, 0xFF)
rowNumber := shr(8, tick)
}
uint256 _row = pool.tickTable(rowNumber) << (255 - bitNumber); // all the 1s at or to the right of the current bitNumber
if (_row != 0) {
tick -= int24(255 - getMostSignificantBit(_row));
return (tick * tickSpacing, true);
} else {
tick -= int24(bitNumber);
return (tick * tickSpacing, false);
}
} else {
// start from the word of the next tick, since the current tick state doesn't matter
tick += 1;
int16 rowNumber;
uint8 bitNumber;
assembly {
bitNumber := and(tick, 0xFF)
rowNumber := shr(8, tick)
}
// all the 1s at or to the left of the bitNumber
uint256 _row = pool.tickTable(rowNumber) >> (bitNumber);
if (_row != 0) {
tick += int24(getSingleSignificantBit(-_row & _row)); // least significant bit
return (tick * tickSpacing, true);
} else {
tick += int24(255 - bitNumber);
return (tick * tickSpacing, false);
}
}
}
/// @notice get position of single 1-bit
/// @dev it is assumed that word contains exactly one 1-bit, otherwise the result will be incorrect
/// @param word The word containing only one 1-bit
function getSingleSignificantBit(uint256 word) internal pure returns (uint8 singleBitPos) {
assembly {
singleBitPos := iszero(and(word, 0x5555555555555555555555555555555555555555555555555555555555555555))
singleBitPos := or(
singleBitPos,
shl(7, iszero(and(word, 0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)))
)
singleBitPos := or(
singleBitPos,
shl(6, iszero(and(word, 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF)))
)
singleBitPos := or(
singleBitPos,
shl(5, iszero(and(word, 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF)))
)
singleBitPos := or(
singleBitPos,
shl(4, iszero(and(word, 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF)))
)
singleBitPos := or(
singleBitPos,
shl(3, iszero(and(word, 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF)))
)
singleBitPos := or(
singleBitPos,
shl(2, iszero(and(word, 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F)))
)
singleBitPos := or(
singleBitPos,
shl(1, iszero(and(word, 0x3333333333333333333333333333333333333333333333333333333333333333)))
)
}
}
/// @notice get position of most significant 1-bit (leftmost)
/// @dev it is assumed that before the call, a check will be made that the argument (word) is not equal to zero
/// @param word The word containing at least one 1-bit
function getMostSignificantBit(uint256 word) internal pure returns (uint8 mostBitPos) {
assembly {
word := or(word, shr(1, word))
word := or(word, shr(2, word))
word := or(word, shr(4, word))
word := or(word, shr(8, word))
word := or(word, shr(16, word))
word := or(word, shr(32, word))
word := or(word, shr(64, word))
word := or(word, shr(128, word))
word := sub(word, shr(1, word))
}
return (getSingleSignificantBit(word));
}
/// @inheritdoc MultiQuoter
function pathHasMultiplePools(bytes memory path) internal pure override returns (bool) {
return path.hasMultiplePools();
}
/// @inheritdoc MultiQuoter
function pathSkipToken(bytes memory path) internal pure override returns (bytes memory) {
return path.skipToken();
}
}

View File

@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./AlgebraCommon.sol";
import "./interfaces/IMultiQuoter.sol";
contract AlgebraSampler is AlgebraCommon {
/// @dev Sample sell quotes from Algebra.
/// @param quoter Algebra MultiQuoter contract.
/// @param factory Algebra Factory contract.
/// @param tokenPath Token route. Should be takerToken --> makerToken.
/// @param inputAmounts Taker token sell amount for each sample.
/// @return path The encoded path for each sample.
/// @return gasEstimates Estimated amount of gas used
/// @return outputAmounts Maker amounts bought at each taker token amount.
function sampleSellsFromAlgebra(
IMultiQuoter quoter,
address factory,
address[] memory tokenPath,
uint256[] memory inputAmounts
) public view returns (bytes memory path, uint256[] memory gasEstimates, uint256[] memory outputAmounts) {
outputAmounts = new uint256[](inputAmounts.length);
gasEstimates = new uint256[](inputAmounts.length);
if (!isValidTokenPath(factory, tokenPath)) {
return (path, gasEstimates, outputAmounts);
}
path = toAlgebraPath(tokenPath);
try quoter.quoteExactMultiInput(factory, path, inputAmounts) {} catch (bytes memory reason) {
(, outputAmounts, gasEstimates) = decodeMultiSwapRevert(reason);
}
}
/// @dev Sample buy quotes from Algebra.
/// @param quoter Algebra MultiQuoter contract.
/// @param factory UniswapV3 Factory contract.
/// @param tokenPath Token route. Should be takerToken -> makerToken.
/// @param outputAmounts Maker token buy amount for each sample.
/// @return path The encoded path for each sample.
/// @return gasEstimates Estimated amount of gas used
/// @return inputAmounts Taker amounts sold at each maker token amount.
function sampleBuysFromAlgebra(
IMultiQuoter quoter,
address factory,
address[] memory tokenPath,
uint256[] memory outputAmounts
) public view returns (bytes memory path, uint256[] memory gasEstimates, uint256[] memory inputAmounts) {
inputAmounts = new uint256[](outputAmounts.length);
gasEstimates = new uint256[](outputAmounts.length);
address[] memory reverseTokenPath = reverseAddressPath(tokenPath);
if (!isValidTokenPath(factory, reverseTokenPath)) {
return (path, gasEstimates, inputAmounts);
}
path = toAlgebraPath(tokenPath);
bytes memory reversePath = toAlgebraPath(reverseTokenPath);
try quoter.quoteExactMultiOutput(factory, reversePath, outputAmounts) {} catch (bytes memory reason) {
(, inputAmounts, gasEstimates) = decodeMultiSwapRevert(reason);
}
}
}

View File

@ -0,0 +1,113 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
contract ApproximateBuys {
/// @dev Information computing buy quotes for sources that do not have native
/// buy quote support.
struct ApproximateBuyQuoteOpts {
// Arbitrary maker token data to pass to `getSellQuoteCallback`.
bytes makerTokenData;
// Arbitrary taker token data to pass to `getSellQuoteCallback`.
bytes takerTokenData;
// Callback to retrieve a sell quote.
function(bytes memory, bytes memory, uint256) internal view returns (uint256) getSellQuoteCallback;
}
uint256 private constant ONE_HUNDED_PERCENT_BPS = 1e4;
/// @dev Maximum approximate (positive) error rate when approximating a buy quote.
uint256 private constant APPROXIMATE_BUY_TARGET_EPSILON_BPS = 0.0005e4;
/// @dev Maximum iterations to perform when approximating a buy quote.
uint256 private constant APPROXIMATE_BUY_MAX_ITERATIONS = 5;
function _sampleApproximateBuys(
ApproximateBuyQuoteOpts memory opts,
uint256[] memory makerTokenAmounts
) internal view returns (uint256[] memory takerTokenAmounts) {
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
if (makerTokenAmounts.length == 0) {
return takerTokenAmounts;
}
uint256 sellAmount = opts.getSellQuoteCallback(opts.makerTokenData, opts.takerTokenData, makerTokenAmounts[0]);
if (sellAmount == 0) {
return takerTokenAmounts;
}
uint256 buyAmount = opts.getSellQuoteCallback(opts.takerTokenData, opts.makerTokenData, sellAmount);
if (buyAmount == 0) {
return takerTokenAmounts;
}
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
uint256 eps = 0;
for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) {
// adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
sellAmount = _safeGetPartialAmountCeil(makerTokenAmounts[i], buyAmount, sellAmount);
if (sellAmount == 0) {
break;
}
sellAmount = _safeGetPartialAmountCeil(
(ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS),
ONE_HUNDED_PERCENT_BPS,
sellAmount
);
if (sellAmount == 0) {
break;
}
uint256 _buyAmount = opts.getSellQuoteCallback(opts.takerTokenData, opts.makerTokenData, sellAmount);
if (_buyAmount == 0) {
break;
}
// We re-use buyAmount next iteration, only assign if it is
// non zero
buyAmount = _buyAmount;
// If we've reached our goal, exit early
if (buyAmount >= makerTokenAmounts[i]) {
eps = ((buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS) / makerTokenAmounts[i];
if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
break;
}
}
}
if (eps == 0 || eps > APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
break;
}
// We do our best to close in on the requested amount, but we can either over buy or under buy and exit
// if we hit a max iteration limit
// We scale the sell amount to get the approximate target
takerTokenAmounts[i] = _safeGetPartialAmountCeil(makerTokenAmounts[i], buyAmount, sellAmount);
}
}
function _safeGetPartialAmountCeil(
uint256 numerator,
uint256 denominator,
uint256 target
) internal view returns (uint256 partialAmount) {
if (numerator == 0 || target == 0 || denominator == 0) return 0;
uint256 c = numerator * target;
if (c / numerator != target) return 0;
return (c + (denominator - 1)) / denominator;
}
}

View File

@ -0,0 +1,129 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
// ERC20 contract interface
abstract contract IToken {
/// @dev Query the balance of owner
/// @param _owner The address from which the balance will be retrieved
/// @return Balance of owner
function balanceOf(address _owner) public view virtual returns (uint256);
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return Amount of remaining tokens allowed to spent
function allowance(address _owner, address _spender) public view virtual returns (uint256);
}
contract BalanceChecker {
/*
Check the token balances of wallet-token pairs.
Pass 0xeee... as a "token" address to get ETH balance.
Possible error throws:
- extremely large arrays for user and or tokens (gas cost too high)
Returns a one-dimensional that's user.length long.
*/
function balances(address[] calldata users, address[] calldata tokens) external view returns (uint256[] memory) {
// make sure the users array and tokens array are of equal length
require(users.length == tokens.length, "users array is a different length than the tokens array");
uint256[] memory addrBalances = new uint256[](users.length);
for (uint256 i = 0; i < users.length; i++) {
if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) {
addrBalances[i] = IToken(tokens[i]).balanceOf(users[i]);
} else {
addrBalances[i] = users[i].balance; // ETH balance
}
}
return addrBalances;
}
/*
Check the token balances of wallet-token pairs with a spender contract for an allowance check.
Pass 0xeee... as a "token" address to get ETH balance.
Possible error throws:
- extremely large arrays for user and or tokens (gas cost too high)
Returns a one-dimensional that's user.length long. It is the lesser of balance and allowance
*/
function getMinOfBalancesOrAllowances(
address[] calldata users,
address[] calldata tokens,
address spender
) external view returns (uint256[] memory) {
// make sure the users array and tokens array are of equal length
require(users.length == tokens.length, "users array is a different length than the tokens array");
uint256[] memory addrBalances = new uint256[](users.length);
for (uint256 i = 0; i < users.length; i++) {
if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) {
uint256 balance;
uint256 allowance;
balance = IToken(tokens[i]).balanceOf(users[i]);
allowance = IToken(tokens[i]).allowance(users[i], spender);
if (allowance < balance) {
addrBalances[i] = allowance;
} else {
addrBalances[i] = balance;
}
} else {
addrBalances[i] = users[i].balance; // ETH balance
}
}
return addrBalances;
}
/*
Check the allowances of an array of owner-spender-tokens
Returns 0 for 0xeee... (ETH)
Possible error throws:
- extremely large arrays for user and or tokens (gas cost too high)
Returns a one-dimensional array that's owners.length long.
*/
function allowances(
address[] calldata owners,
address[] calldata spenders,
address[] calldata tokens
) external view returns (uint256[] memory) {
// make sure the arrays are all of equal length
require(owners.length == spenders.length, "all arrays must be of equal length");
require(owners.length == tokens.length, "all arrays must be of equal length");
uint256[] memory addrAllowances = new uint256[](owners.length);
for (uint256 i = 0; i < owners.length; i++) {
if (tokens[i] != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) {
addrAllowances[i] = IToken(tokens[i]).allowance(owners[i], spenders[i]);
} else {
// ETH
addrAllowances[i] = 0;
}
}
return addrAllowances;
}
}

View File

@ -0,0 +1,177 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IBalancer.sol";
contract BalancerSampler {
/// @dev Base gas limit for Balancer calls.
uint256 private constant BALANCER_CALL_GAS = 300e3; // 300k
// Balancer math constants
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol
uint256 private constant BONE = 10 ** 18;
uint256 private constant MAX_IN_RATIO = BONE / 2;
uint256 private constant MAX_OUT_RATIO = (BONE / 3) + 1 wei;
struct BalancerState {
uint256 takerTokenBalance;
uint256 makerTokenBalance;
uint256 takerTokenWeight;
uint256 makerTokenWeight;
uint256 swapFee;
}
/// @dev Sample sell quotes from Balancer.
/// @param poolAddress Address of the Balancer pool to query.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromBalancer(
address poolAddress,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
IBalancer pool = IBalancer(poolAddress);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
return makerTokenAmounts;
}
BalancerState memory poolState;
poolState.takerTokenBalance = pool.getBalance(takerToken);
poolState.makerTokenBalance = pool.getBalance(makerToken);
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
poolState.swapFee = pool.getSwapFee();
for (uint256 i = 0; i < numSamples; i++) {
// Handles this revert scenario:
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443
if (takerTokenAmounts[i] > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) {
break;
}
try
pool.calcOutGivenIn{gas: BALANCER_CALL_GAS}(
poolState.takerTokenBalance,
poolState.takerTokenWeight,
poolState.makerTokenBalance,
poolState.makerTokenWeight,
takerTokenAmounts[i],
poolState.swapFee
)
returns (uint256 amount) {
makerTokenAmounts[i] = amount;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
/// @dev Sample buy quotes from Balancer.
/// @param poolAddress Address of the Balancer pool to query.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromBalancer(
address poolAddress,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
IBalancer pool = IBalancer(poolAddress);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
return takerTokenAmounts;
}
BalancerState memory poolState;
poolState.takerTokenBalance = pool.getBalance(takerToken);
poolState.makerTokenBalance = pool.getBalance(makerToken);
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
poolState.swapFee = pool.getSwapFee();
for (uint256 i = 0; i < numSamples; i++) {
// Handles this revert scenario:
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L505
if (makerTokenAmounts[i] > _bmul(poolState.makerTokenBalance, MAX_OUT_RATIO)) {
break;
}
try
pool.calcInGivenOut{gas: BALANCER_CALL_GAS}(
poolState.takerTokenBalance,
poolState.takerTokenWeight,
poolState.makerTokenBalance,
poolState.makerTokenWeight,
makerTokenAmounts[i],
poolState.swapFee
)
returns (uint256 amount) {
// Handles this revert scenario:
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443
if (amount > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) {
break;
}
takerTokenAmounts[i] = amount;
// Break early if there are 0 amounts
if (takerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
/// @dev Hacked version of Balancer's `bmul` function, returning 0 instead
/// of reverting.
/// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol#L63-L73
/// @param a The first operand.
/// @param b The second operand.
/// @param c The result of the multiplication, or 0 if `bmul` would've reverted.
function _bmul(uint256 a, uint256 b) private pure returns (uint256 c) {
uint256 c0 = a * b;
if (a != 0 && c0 / a != b) {
return 0;
}
uint256 c1 = c0 + (BONE / 2);
if (c1 < c0) {
return 0;
}
uint256 c2 = c1 / BONE;
return c2;
}
}

View File

@ -0,0 +1,100 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IBalancerV2Vault.sol";
import "./BalancerV2Common.sol";
contract BalancerV2BatchSampler is BalancerV2Common {
// Replaces amount for first step with each takerTokenAmount and calls queryBatchSwap using supplied steps
/// @dev Sample sell quotes from Balancer V2 supporting multihops.
/// @param swapSteps Array of swap steps (can be >= 1).
/// @param swapAssets Array of token address for swaps.
/// @param takerTokenAmounts Taker token sell amount for each sample.
function sampleMultihopSellsFromBalancerV2(
IBalancerV2Vault vault,
IBalancerV2Vault.BatchSwapStep[] memory swapSteps,
address[] memory swapAssets,
uint256[] memory takerTokenAmounts
) public returns (uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
IBalancerV2Vault.FundManagement memory swapFunds = _createSwapFunds();
for (uint256 i = 0; i < numSamples; i++) {
swapSteps[0].amount = takerTokenAmounts[i];
try
// For sells we specify the takerToken which is what the vault will receive from the trade
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_IN, swapSteps, swapAssets, swapFunds)
returns (
// amounts represent pool balance deltas from the swap (incoming balance, outgoing balance)
int256[] memory amounts
) {
// Outgoing balance is negative so we need to flip the sign
// Note - queryBatchSwap will return a delta for each token in the assets array and last asset should be tokenOut
int256 amountOutFromPool = amounts[amounts.length - 1] * -1;
if (amountOutFromPool <= 0) {
break;
}
makerTokenAmounts[i] = uint256(amountOutFromPool);
} catch {
// Swallow failures, leaving all results as zero.
break;
}
}
}
// Replaces amount for first step with each makerTokenAmount and calls queryBatchSwap using supplied steps
/// @dev Sample buy quotes from Balancer V2 supporting multihops.
/// @param swapSteps Array of swap steps (can be >= 1).
/// @param swapAssets Array of token address for swaps.
/// @param makerTokenAmounts Maker token buy amount for each sample.
function sampleMultihopBuysFromBalancerV2(
IBalancerV2Vault vault,
IBalancerV2Vault.BatchSwapStep[] memory swapSteps,
address[] memory swapAssets,
uint256[] memory makerTokenAmounts
) public returns (uint256[] memory takerTokenAmounts) {
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
IBalancerV2Vault.FundManagement memory swapFunds = _createSwapFunds();
for (uint256 i = 0; i < numSamples; i++) {
swapSteps[0].amount = makerTokenAmounts[i];
try
// Uses GIVEN_OUT type for Buy
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_OUT, swapSteps, swapAssets, swapFunds)
returns (
// amounts represent pool balance deltas from the swap (incoming balance, outgoing balance)
int256[] memory amounts
) {
int256 amountIntoPool = amounts[0];
if (amountIntoPool <= 0) {
break;
}
takerTokenAmounts[i] = uint256(amountIntoPool);
} catch {
// Swallow failures, leaving all results as zero.
break;
}
}
}
}

View File

@ -1,27 +1,35 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 ZeroEx Intl.
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "../src/features/UniswapV3Feature.sol";
contract TestUniswapV3Feature is UniswapV3Feature {
constructor(
IEtherToken weth,
address uniFactory,
bytes32 poolInitCodeHash
) public UniswapV3Feature(weth, uniFactory, poolInitCodeHash) {}
import "./interfaces/IBalancerV2Vault.sol";
receive() external payable {}
contract BalancerV2Common {
function _createSwapFunds() internal view returns (IBalancerV2Vault.FundManagement memory) {
return
IBalancerV2Vault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
}
}

View File

@ -0,0 +1,121 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IBancor.sol";
contract BancorSampler {
/// @dev Base gas limit for Bancor calls.
uint256 private constant BANCOR_CALL_GAS = 300e3; // 300k
struct BancorSamplerOpts {
IBancorRegistry registry;
address[][] paths;
}
/// @dev Sample sell quotes from Bancor.
/// @param opts BancorSamplerOpts The Bancor registry contract address and paths
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return bancorNetwork the Bancor Network address
/// @return path the selected conversion path from bancor
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromBancor(
BancorSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (address bancorNetwork, address[] memory path, uint256[] memory makerTokenAmounts) {
makerTokenAmounts = new uint256[](takerTokenAmounts.length);
if (opts.paths.length == 0) {
return (bancorNetwork, path, makerTokenAmounts);
}
(bancorNetwork, path) = _findBestPath(opts, takerToken, makerToken, takerTokenAmounts);
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
try IBancorNetwork(bancorNetwork).rateByPath{gas: BANCOR_CALL_GAS}(path, takerTokenAmounts[i]) returns (
uint256 amount
) {
makerTokenAmounts[i] = amount;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
} catch {
// Swallow failures, leaving all results as zero.
break;
}
}
return (bancorNetwork, path, makerTokenAmounts);
}
/// @dev Sample buy quotes from Bancor. Unimplemented
/// @param opts BancorSamplerOpts The Bancor registry contract address and paths
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return bancorNetwork the Bancor Network address
/// @return path the selected conversion path from bancor
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromBancor(
BancorSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (address bancorNetwork, address[] memory path, uint256[] memory takerTokenAmounts) {}
function _findBestPath(
BancorSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) internal view returns (address bancorNetwork, address[] memory path) {
bancorNetwork = opts.registry.getAddress(opts.registry.BANCOR_NETWORK());
if (opts.paths.length == 0) {
return (bancorNetwork, path);
}
uint256 maxBoughtAmount = 0;
// Find the best path by selling the largest taker amount
for (uint256 i = 0; i < opts.paths.length; i++) {
if (opts.paths[i].length < 2) {
continue;
}
try
IBancorNetwork(bancorNetwork).rateByPath{gas: BANCOR_CALL_GAS}(
opts.paths[i],
takerTokenAmounts[takerTokenAmounts.length - 1]
)
returns (uint256 amount) {
if (amount > maxBoughtAmount) {
maxBoughtAmount = amount;
path = opts.paths[i];
}
} catch {
// Swallow failures, leaving all results as zero.
continue;
}
}
}
}

View File

@ -0,0 +1,108 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IBancorV3.sol";
contract BancorV3Sampler {
/// @dev Gas limit for BancorV3 calls.
uint256 private constant BancorV3_CALL_GAS = 150e3; // 150k
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev Sample sell quotes from BancorV3.
/// @param weth The WETH contract address
/// @param router Router to look up tokens and amounts
/// @param path Token route. Should be takerToken -> makerToken
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromBancorV3(
address weth,
address router,
address[] memory path,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
if (path[0] == weth) {
path[0] = ETH;
}
if (path[1] == weth) {
path[1] = ETH;
}
for (uint256 i = 0; i < numSamples; i++) {
try IBancorV3(router).tradeOutputBySourceAmount(path[0], path[1], takerTokenAmounts[i]) returns (
uint256 amount
) {
makerTokenAmounts[i] = amount;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
/// @dev Sample buy quotes from BancorV3.
/// @param weth The WETH contract address
/// @param router Router to look up tokens and amounts
/// @param path Token route. Should be takerToken -> makerToken.
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromBancorV3(
address weth,
address router,
address[] memory path,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
if (path[0] == weth) {
path[0] = ETH;
}
if (path[1] == weth) {
path[1] = ETH;
}
for (uint256 i = 0; i < numSamples; i++) {
try IBancorV3(router).tradeInputByTargetAmount(path[0], path[1], makerTokenAmounts[i]) returns (
uint256 amount
) {
takerTokenAmounts[i] = amount;
// Break early if there are 0 amounts
if (takerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
}

View File

@ -0,0 +1,99 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./SamplerUtils.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
// Minimal CToken interface
interface ICToken {
function mint(uint256 mintAmount) external returns (uint256);
function redeem(uint256 redeemTokens) external returns (uint256);
function redeemUnderlying(uint256 redeemAmount) external returns (uint256);
function exchangeRateStored() external view returns (uint256);
function decimals() external view returns (uint8);
}
contract CompoundSampler is SamplerUtils {
uint256 private constant EXCHANGE_RATE_SCALE = 1e10;
function sampleSellsFromCompound(
ICToken cToken,
IERC20TokenV06 takerToken,
IERC20TokenV06 makerToken,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
uint256 exchangeRate = cToken.exchangeRateStored();
uint256 cTokenDecimals = uint256(cToken.decimals());
if (address(makerToken) == address(cToken)) {
// mint
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] =
(takerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals) /
exchangeRate;
}
} else if (address(takerToken) == address(cToken)) {
// redeem
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] =
(takerTokenAmounts[i] * exchangeRate) /
(EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
}
}
}
function sampleBuysFromCompound(
ICToken cToken,
IERC20TokenV06 takerToken,
IERC20TokenV06 makerToken,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
uint256 exchangeRate = cToken.exchangeRateStored();
uint256 cTokenDecimals = uint256(cToken.decimals());
if (address(makerToken) == address(cToken)) {
// mint
for (uint256 i = 0; i < numSamples; i++) {
takerTokenAmounts[i] =
(makerTokenAmounts[i] * exchangeRate) /
(EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
}
} else if (address(takerToken) == address(cToken)) {
// redeem
for (uint256 i = 0; i < numSamples; i++) {
takerTokenAmounts[i] =
(makerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals) /
exchangeRate;
}
}
}
}

View File

@ -0,0 +1,145 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/ICurve.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract CurveSampler is SamplerUtils, ApproximateBuys {
/// @dev Information for sampling from curve sources.
struct CurveInfo {
address poolAddress;
bytes4 sellQuoteFunctionSelector;
bytes4 buyQuoteFunctionSelector;
}
/// @dev Base gas limit for Curve calls. Some Curves have multiple tokens
/// So a reasonable ceil is 150k per token. Biggest Curve has 4 tokens.
uint256 private constant CURVE_CALL_GAS = 2000e3; // Was 600k for Curve but SnowSwap is using 1500k+
/// @dev Sample sell quotes from Curve.
/// @param curveInfo Curve information specific to this token pair.
/// @param fromTokenIdx Index of the taker token (what to sell).
/// @param toTokenIdx Index of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromCurve(
CurveInfo memory curveInfo,
int128 fromTokenIdx,
int128 toTokenIdx,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
(bool didSucceed, bytes memory resultData) = curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
abi.encodeWithSelector(
curveInfo.sellQuoteFunctionSelector,
fromTokenIdx,
toTokenIdx,
takerTokenAmounts[i]
)
);
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
}
makerTokenAmounts[i] = buyAmount;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
}
}
/// @dev Sample buy quotes from Curve.
/// @param curveInfo Curve information specific to this token pair.
/// @param fromTokenIdx Index of the taker token (what to sell).
/// @param toTokenIdx Index of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromCurve(
CurveInfo memory curveInfo,
int128 fromTokenIdx,
int128 toTokenIdx,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
if (curveInfo.buyQuoteFunctionSelector == bytes4(0)) {
// Buys not supported on this curve, so approximate it.
return
_sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(toTokenIdx, curveInfo),
takerTokenData: abi.encode(fromTokenIdx, curveInfo),
getSellQuoteCallback: _sampleSellForApproximateBuyFromCurve
}),
makerTokenAmounts
);
}
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
(bool didSucceed, bytes memory resultData) = curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
abi.encodeWithSelector(
curveInfo.buyQuoteFunctionSelector,
fromTokenIdx,
toTokenIdx,
makerTokenAmounts[i]
)
);
uint256 sellAmount = 0;
if (didSucceed) {
sellAmount = abi.decode(resultData, (uint256));
}
takerTokenAmounts[i] = sellAmount;
// Break early if there are 0 amounts
if (takerTokenAmounts[i] == 0) {
break;
}
}
}
function _sampleSellForApproximateBuyFromCurve(
bytes memory takerTokenData,
bytes memory makerTokenData,
uint256 sellAmount
) private view returns (uint256 buyAmount) {
(int128 takerTokenIdx, CurveInfo memory curveInfo) = abi.decode(takerTokenData, (int128, CurveInfo));
int128 makerTokenIdx = abi.decode(makerTokenData, (int128));
(bool success, bytes memory resultData) = address(this).staticcall(
abi.encodeWithSelector(
this.sampleSellsFromCurve.selector,
curveInfo,
takerTokenIdx,
makerTokenIdx,
_toSingleValueArray(sellAmount)
)
);
if (!success) {
return 0;
}
// solhint-disable-next-line indent
return abi.decode(resultData, (uint256[]))[0];
}
}

View File

@ -0,0 +1,184 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
interface IDODOZoo {
function getDODO(address baseToken, address quoteToken) external view returns (address);
}
interface IDODOHelper {
function querySellQuoteToken(address dodo, uint256 amount) external view returns (uint256);
}
interface IDODO {
function querySellBaseToken(uint256 amount) external view returns (uint256);
function _TRADE_ALLOWED_() external view returns (bool);
}
contract DODOSampler is SamplerUtils, ApproximateBuys {
/// @dev Gas limit for DODO calls.
uint256 private constant DODO_CALL_GAS = 300e3; // 300k
struct DODOSamplerOpts {
address registry;
address helper;
}
/// @dev Sample sell quotes from DODO.
/// @param opts DODOSamplerOpts DODO Registry and helper addresses
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromDODO(
DODOSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken);
address baseToken;
// If pool exists we have the correct order of Base/Quote
if (pool != address(0)) {
baseToken = takerToken;
sellBase = true;
} else {
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
// No pool either direction
if (address(pool) == address(0)) {
return (sellBase, pool, makerTokenAmounts);
}
baseToken = makerToken;
sellBase = false;
}
// DODO Pool has been disabled
if (!IDODO(pool)._TRADE_ALLOWED_()) {
return (sellBase, pool, makerTokenAmounts);
}
for (uint256 i = 0; i < numSamples; i++) {
uint256 buyAmount = _sampleSellForApproximateBuyFromDODO(
abi.encode(takerToken, pool, baseToken, opts.helper), // taker token data
abi.encode(makerToken, pool, baseToken, opts.helper), // maker token data
takerTokenAmounts[i]
);
makerTokenAmounts[i] = buyAmount;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
}
}
/// @dev Sample buy quotes from DODO.
/// @param opts DODOSamplerOpts DODO Registry and helper addresses
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @return sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromDODO(
DODOSamplerOpts memory opts,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
// Pool is BASE/QUOTE
// Look up the pool from the taker/maker combination
pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken);
address baseToken;
// If pool exists we have the correct order of Base/Quote
if (pool != address(0)) {
baseToken = takerToken;
sellBase = true;
} else {
// Look up the pool from the maker/taker combination
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
// No pool either direction
if (address(pool) == address(0)) {
return (sellBase, pool, takerTokenAmounts);
}
baseToken = makerToken;
sellBase = false;
}
// DODO Pool has been disabled
if (!IDODO(pool)._TRADE_ALLOWED_()) {
return (sellBase, pool, takerTokenAmounts);
}
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken, pool, baseToken, opts.helper),
takerTokenData: abi.encode(takerToken, pool, baseToken, opts.helper),
getSellQuoteCallback: _sampleSellForApproximateBuyFromDODO
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromDODO(
bytes memory takerTokenData,
bytes memory /* makerTokenData */,
uint256 sellAmount
) private view returns (uint256) {
(address takerToken, address pool, address baseToken, address helper) = abi.decode(
takerTokenData,
(address, address, address, address)
);
// We will get called to sell both the taker token and also to sell the maker token
if (takerToken == baseToken) {
// If base token then use the original query on the pool
try IDODO(pool).querySellBaseToken{gas: DODO_CALL_GAS}(sellAmount) returns (uint256 amount) {
return amount;
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
return 0;
}
} else {
// If quote token then use helper, this is less accurate
try IDODOHelper(helper).querySellQuoteToken{gas: DODO_CALL_GAS}(pool, sellAmount) returns (uint256 amount) {
return amount;
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
return 0;
}
}
}
}

View File

@ -0,0 +1,174 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
interface IDODOV2Registry {
function getDODOPool(address baseToken, address quoteToken) external view returns (address[] memory machines);
}
interface IDODOV2Pool {
function querySellBase(
address trader,
uint256 payBaseAmount
) external view returns (uint256 receiveQuoteAmount, uint256 mtFee);
function querySellQuote(
address trader,
uint256 payQuoteAmount
) external view returns (uint256 receiveBaseAmount, uint256 mtFee);
}
contract DODOV2Sampler is SamplerUtils, ApproximateBuys {
/// @dev Gas limit for DODO V2 calls.
uint256 private constant DODO_V2_CALL_GAS = 300e3; // 300k
/// @dev Sample sell quotes from DODO V2.
/// @param registry Address of the registry to look up.
/// @param offset offset index for the pool in the registry.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromDODOV2(
address registry,
uint256 offset,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
if (pool == address(0)) {
return (sellBase, pool, makerTokenAmounts);
}
for (uint256 i = 0; i < numSamples; i++) {
uint256 buyAmount = _sampleSellForApproximateBuyFromDODOV2(
abi.encode(takerToken, pool, sellBase), // taker token data
abi.encode(makerToken, pool, sellBase), // maker token data
takerTokenAmounts[i]
);
makerTokenAmounts[i] = buyAmount;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
}
}
/// @dev Sample buy quotes from DODO.
/// @param registry Address of the registry to look up.
/// @param offset offset index for the pool in the registry.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @return sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromDODOV2(
address registry,
uint256 offset,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
if (pool == address(0)) {
return (sellBase, pool, takerTokenAmounts);
}
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken, pool, !sellBase),
takerTokenData: abi.encode(takerToken, pool, sellBase),
getSellQuoteCallback: _sampleSellForApproximateBuyFromDODOV2
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromDODOV2(
bytes memory takerTokenData,
bytes memory /* makerTokenData */,
uint256 sellAmount
) private view returns (uint256) {
(address takerToken, address pool, bool sellBase) = abi.decode(takerTokenData, (address, address, bool));
// We will get called to sell both the taker token and also to sell the maker token
// since we use approximate buy for sell and buy functions
if (sellBase) {
try IDODOV2Pool(pool).querySellBase{gas: DODO_V2_CALL_GAS}(address(0), sellAmount) returns (
uint256 amount,
uint256
) {
return amount;
} catch {
return 0;
}
} else {
try IDODOV2Pool(pool).querySellQuote{gas: DODO_V2_CALL_GAS}(address(0), sellAmount) returns (
uint256 amount,
uint256
) {
return amount;
} catch {
return 0;
}
}
}
function _getNextDODOV2Pool(
address registry,
uint256 offset,
address takerToken,
address makerToken
) internal view returns (address machine, bool sellBase) {
// Query in base -> quote direction, if a pool is found then we are selling the base
address[] memory machines = IDODOV2Registry(registry).getDODOPool(takerToken, makerToken);
sellBase = true;
if (machines.length == 0) {
// Query in quote -> base direction, if a pool is found then we are selling the quote
machines = IDODOV2Registry(registry).getDODOPool(makerToken, takerToken);
sellBase = false;
}
if (offset >= machines.length) {
return (address(0), false);
}
machine = machines[offset];
}
}

View File

@ -0,0 +1,104 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./AaveSampler.sol";
import "./AlgebraSampler.sol";
import "./BalancerSampler.sol";
import "./BalancerV2BatchSampler.sol";
import "./BancorSampler.sol";
import "./BancorV3Sampler.sol";
import "./CompoundSampler.sol";
import "./CurveSampler.sol";
import "./DODOSampler.sol";
import "./DODOV2Sampler.sol";
import "./GMXSampler.sol";
import "./KyberDmmSampler.sol";
import "./LidoSampler.sol";
import "./MakerPSMSampler.sol";
import "./MStableSampler.sol";
import "./MooniswapSampler.sol";
import "./NativeOrderSampler.sol";
import "./PlatypusSampler.sol";
import "./ShellSampler.sol";
import "./SynthetixSampler.sol";
import "./TraderJoeV2Sampler.sol";
import "./TwoHopSampler.sol";
import "./UniswapSampler.sol";
import "./UniswapV2Sampler.sol";
import "./UniswapV3Sampler.sol";
import "./VelodromeSampler.sol";
import "./WooPPSampler.sol";
import "./UtilitySampler.sol";
import "./KyberElasticSampler.sol";
contract ERC20BridgeSampler is
AaveSampler,
AlgebraSampler,
BalancerSampler,
BalancerV2BatchSampler,
BancorSampler,
BancorV3Sampler,
CompoundSampler,
CurveSampler,
DODOSampler,
DODOV2Sampler,
GMXSampler,
KyberDmmSampler,
LidoSampler,
MakerPSMSampler,
MStableSampler,
MooniswapSampler,
NativeOrderSampler,
PlatypusSampler,
ShellSampler,
SynthetixSampler,
TraderJoeV2Sampler,
TwoHopSampler,
UniswapSampler,
UniswapV2Sampler,
UniswapV3Sampler,
VelodromeSampler,
WooPPSampler,
UtilitySampler,
KyberElasticSampler
{
struct CallResults {
bytes data;
bool success;
}
/// @dev Call multiple public functions on this contract in a single transaction.
/// @param callDatas ABI-encoded call data for each function call.
/// @return callResults ABI-encoded results data for each call.
function batchCall(bytes[] calldata callDatas) external returns (CallResults[] memory callResults) {
callResults = new CallResults[](callDatas.length);
for (uint256 i = 0; i != callDatas.length; ++i) {
callResults[i].success = true;
if (callDatas[i].length == 0) {
continue;
}
(callResults[i].success, callResults[i].data) = address(this).call(callDatas[i]);
}
}
receive() external payable {}
}

View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
contract FakeTaker {
struct Result {
bool success;
bytes resultData;
uint256 gasUsed;
}
receive() external payable {}
function execute(address payable to, bytes calldata data) public payable returns (Result memory result) {
uint256 gasBefore = gasleft();
(result.success, result.resultData) = to.call{value: msg.value}(data);
result.gasUsed = gasBefore - gasleft();
}
}

View File

@ -0,0 +1,94 @@
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IGMX.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract GMXSampler is SamplerUtils, ApproximateBuys {
struct GMXInfo {
address reader;
address vault;
address[] path;
}
function sampleSellsFromGMX(
IGMX reader,
address vault,
address[] memory path,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
try IGMX(reader).getMaxAmountIn(IVault(vault), path[0], path[1]) returns (uint256 maxAmountIn) {
// Break early if GMX does not have enough liquidity to perform the swap
if (takerTokenAmounts[i] > maxAmountIn) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
try IGMX(reader).getAmountOut(IVault(vault), path[0], path[1], takerTokenAmounts[i]) returns (
uint256 amountAfterFees,
uint256 feeAmount
) {
makerTokenAmounts[i] = amountAfterFees;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
function sampleBuysFromGMX(
IGMX reader,
address vault,
address[] memory path,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
address[] memory invertBuyPath = new address[](2);
invertBuyPath[0] = path[1];
invertBuyPath[1] = path[0];
return
_sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(address(reader), vault, invertBuyPath),
takerTokenData: abi.encode(address(reader), vault, path),
getSellQuoteCallback: _sampleSellForApproximateBuyFromGMX
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromGMX(
bytes memory takerTokenData,
bytes memory makerTokenData,
uint256 sellAmount
) private view returns (uint256 buyAmount) {
(address _reader, address _vault, address[] memory _path) = abi.decode(
takerTokenData,
(address, address, address[])
);
(bool success, bytes memory resultData) = address(this).staticcall(
abi.encodeWithSelector(
this.sampleSellsFromGMX.selector,
_reader,
_vault,
_path,
_toSingleValueArray(sellAmount)
)
);
if (!success) {
return 0;
}
// solhint-disable-next-line indent
return abi.decode(resultData, (uint256[]))[0];
}
}

View File

@ -0,0 +1,143 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
interface IKyberDmmPool {
function totalSupply() external view returns (uint256);
}
interface IKyberDmmFactory {
function getPools(address token0, address token1) external view returns (address[] memory _tokenPools);
}
interface IKyberDmmRouter {
function factory() external view returns (address);
function getAmountsOut(
uint256 amountIn,
address[] calldata pools,
address[] calldata path
) external view returns (uint256[] memory amounts);
function getAmountsIn(
uint256 amountOut,
address[] calldata pools,
address[] calldata path
) external view returns (uint256[] memory amounts);
}
contract KyberDmmSampler {
/// @dev Gas limit for KyberDmm calls.
uint256 private constant KYBER_DMM_CALL_GAS = 150e3; // 150k
/// @dev Sample sell quotes from KyberDmm.
/// @param router Router to look up tokens and amounts
/// @param path Token route. Should be takerToken -> makerToken
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return pools The pool addresses involved in the multi path trade
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromKyberDmm(
address router,
address[] memory path,
uint256[] memory takerTokenAmounts
) public view returns (address[] memory pools, uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
pools = _getKyberDmmPools(router, path);
if (pools.length == 0) {
return (pools, makerTokenAmounts);
}
for (uint256 i = 0; i < numSamples; i++) {
try
IKyberDmmRouter(router).getAmountsOut{gas: KYBER_DMM_CALL_GAS}(takerTokenAmounts[i], pools, path)
returns (uint256[] memory amounts) {
makerTokenAmounts[i] = amounts[path.length - 1];
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
/// @dev Sample buy quotes from KyberDmm.
/// @param router Router to look up tokens and amounts
/// @param path Token route. Should be takerToken -> makerToken.
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return pools The pool addresses involved in the multi path trade
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromKyberDmm(
address router,
address[] memory path,
uint256[] memory makerTokenAmounts
) public view returns (address[] memory pools, uint256[] memory takerTokenAmounts) {
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
pools = _getKyberDmmPools(router, path);
if (pools.length == 0) {
return (pools, takerTokenAmounts);
}
for (uint256 i = 0; i < numSamples; i++) {
try
IKyberDmmRouter(router).getAmountsIn{gas: KYBER_DMM_CALL_GAS}(makerTokenAmounts[i], pools, path)
returns (uint256[] memory amounts) {
takerTokenAmounts[i] = amounts[0];
// Break early if there are 0 amounts
if (takerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
function _getKyberDmmPools(address router, address[] memory path) private view returns (address[] memory pools) {
IKyberDmmFactory factory = IKyberDmmFactory(IKyberDmmRouter(router).factory());
pools = new address[](path.length - 1);
for (uint256 i = 0; i < pools.length; i++) {
// find the best pool
address[] memory allPools;
try factory.getPools{gas: KYBER_DMM_CALL_GAS}(path[i], path[i + 1]) returns (address[] memory allPools) {
if (allPools.length == 0) {
return new address[](0);
}
uint256 maxSupply = 0;
for (uint256 j = 0; j < allPools.length; j++) {
uint256 totalSupply = IKyberDmmPool(allPools[j]).totalSupply();
if (totalSupply > maxSupply) {
maxSupply = totalSupply;
pools[i] = allPools[j];
}
}
} catch (bytes memory) {
return new address[](0);
}
}
}
}

View File

@ -0,0 +1,141 @@
pragma solidity >=0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IKyberElastic.sol";
import "./TickBasedAMMCommon.sol";
contract KyberElasticCommon is TickBasedAMMCommon {
/// @dev Returns `poolPaths` to sample against. The caller is responsible for not using path involving zero address(es).
function _getPoolPaths(
address factory,
address[] memory path,
uint256 amount
) internal view returns (address[][] memory poolPaths) {
if (path.length == 2) {
return _getPoolPathSingleHop(factory, path, amount);
}
if (path.length == 3) {
return _getPoolPathTwoHop(factory, path, amount);
}
revert("KyberElastic sampler: unsupported token path length");
}
function _getPoolPathSingleHop(
address factory,
address[] memory path,
uint256 amount
) internal view returns (address[][] memory poolPaths) {
poolPaths = new address[][](2);
address[2] memory topPools = _getTopTwoPools(factory, path[0], path[1], amount);
uint256 pathCount = 0;
for (uint256 i = 0; i < 2; i++) {
address topPool = topPools[i];
poolPaths[pathCount] = new address[](1);
poolPaths[pathCount][0] = topPool;
pathCount++;
}
}
function _getPoolPathTwoHop(
address factory,
address[] memory path,
uint256 amount
) internal view returns (address[][] memory poolPaths) {
poolPaths = new address[][](4);
address[2] memory firstHopTopPools = _getTopTwoPools(factory, path[0], path[1], amount);
address[2] memory secondHopTopPools = _getTopTwoPools(factory, path[1], path[2], amount);
uint256 pathCount = 0;
for (uint256 i = 0; i < 2; i++) {
for (uint256 j = 0; j < 2; j++) {
poolPaths[pathCount] = new address[](2);
address[] memory currentPath = poolPaths[pathCount];
currentPath[0] = firstHopTopPools[i];
currentPath[1] = secondHopTopPools[j];
pathCount++;
}
}
}
/// @dev Returns top 0-2 pools and corresponding output amounts based on swaping `inputAmount`.
/// Addresses in `topPools` can be zero addresses when there are pool isn't available.
function _getTopTwoPools(
address factory,
address inputToken,
address outputToken,
uint256 amount
) internal view returns (address[2] memory topPools) {
address[] memory tokenPath = new address[](2);
tokenPath[0] = inputToken;
tokenPath[1] = outputToken;
uint16[5] memory validPoolFees = [uint16(8), uint16(10), uint16(40), uint16(300), uint16(1000)]; //.8 bps, .1 bps, .4 bps, 30 bps, 100 bps
uint128[2] memory topLiquidityAmounts;
for (uint256 i = 0; i < validPoolFees.length; ++i) {
address pool = IKyberElasticFactory(factory).getPool(inputToken, outputToken, validPoolFees[i]);
if (!_isValidPool(pool)) {
continue;
}
// Make sure there is reasonable amount of input token inside the pool.
// This is a tradeoff between lowering latency/gas usage
// vs allowing pools to be sampled for liquidity.
IERC20 token;
token = inputToken < outputToken
? IERC20(IKyberElasticPool(pool).token0())
: IERC20(IKyberElasticPool(pool).token1());
// This threshold was determined by running simbot and seeing where
// pricing starts to degrade vs not filtering.
if (token.balanceOf(pool) < amount / 2) {
continue;
}
(uint128 baseLiquidity, uint128 reinvestLiquidity, ) = IKyberElasticPool(pool).getLiquidityState();
uint128 currLiquidity = baseLiquidity + reinvestLiquidity;
if (currLiquidity > topLiquidityAmounts[0]) {
topLiquidityAmounts[1] = topLiquidityAmounts[0];
topPools[1] = topPools[0];
topLiquidityAmounts[0] = currLiquidity;
topPools[0] = pool;
} else if (currLiquidity > topLiquidityAmounts[1]) {
topLiquidityAmounts[1] = currLiquidity;
topPools[1] = pool;
}
}
}
function _isValidPool(address pool) internal view returns (bool isValid) {
// Check if it has been deployed.
uint256 codeSize;
assembly {
codeSize := extcodesize(pool)
}
return codeSize != 0;
}
function _toPath(address[] memory tokenPath, address[] memory poolPath) internal view returns (bytes memory path) {
require(tokenPath.length >= 2 && tokenPath.length == poolPath.length + 1, "invalid path lengths");
// paths are tightly packed as:
// [token0, token0token1PairFee, token1, token1Token2PairFee, token2, ...]
path = new bytes(tokenPath.length * 20 + poolPath.length * 3);
uint256 o;
assembly {
o := add(path, 32)
}
for (uint256 i = 0; i < tokenPath.length; ++i) {
if (i > 0) {
uint24 poolFee = IKyberElasticPool(poolPath[i - 1]).swapFeeUnits();
assembly {
mstore(o, shl(232, poolFee))
o := add(o, 3)
}
}
address token = tokenPath[i];
assembly {
mstore(o, shl(96, token))
o := add(o, 20)
}
}
}
}

View File

@ -0,0 +1,250 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// transitive dependencies PathHelper.sol and BytesLib.sol require 0.8.9
pragma solidity 0.8.9;
import "@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol";
import "@kybernetwork/ks-elastic-sc/contracts/libraries/TickMath.sol";
import "@kybernetwork/ks-elastic-sc/contracts/libraries/SwapMath.sol";
import "@kybernetwork/ks-elastic-sc/contracts/libraries/Linkedlist.sol";
import "@kybernetwork/ks-elastic-sc/contracts/libraries/LiqDeltaMath.sol";
import "@kybernetwork/ks-elastic-sc/contracts/periphery/libraries/PathHelper.sol";
import "./interfaces/IKyberElastic.sol";
import "./MultiQuoter.sol";
contract KyberElasticMultiQuoter is MultiQuoter {
using SafeCast for uint256;
using LowGasSafeMath for int256;
using SafeCast for int128;
using PathHelper for bytes;
// temporary swap variables, some of which will be used to update the pool state
struct SwapData {
int256 specifiedAmount; // the specified amount (could be tokenIn or tokenOut)
int256 returnedAmount; // the opposite amout of sourceQty
uint160 sqrtP; // current sqrt(price), multiplied by 2^96
int24 currentTick; // the tick associated with the current price
int24 nextTick; // the next initialized tick
uint160 nextSqrtP; // the price of nextTick
bool zeroForOne; // true if the inputs amounts are in terms of token0
bool isExactInput; // true = input qty, false = output qty
uint128 baseL; // the cached base pool liquidity without reinvestment liquidity
uint128 reinvestL; // the cached reinvestment liquidity
uint256 amountsIndex; // index at which amount we're currently swapping to
uint256 gasAggregate; // amount of gas used up to the current tick
uint256 gasResidual; // amount of gas used between current tick and current price
}
/// @inheritdoc MultiQuoter
function getFirstSwapDetails(
address factory,
bytes memory path,
bool isExactInput
) internal view override returns (address pool, bool zeroForOne) {
(address tokenA, address tokenB, uint24 fee) = path.decodeFirstPool();
zeroForOne = tokenA < tokenB;
pool = IKyberElasticFactory(factory).getPool(tokenA, tokenB, fee);
}
/// @dev Return initial data before swapping
/// @param willUpTick whether is up/down tick
/// @return baseL current pool base liquidity without reinvestment liquidity
/// @return reinvestL current pool reinvestment liquidity
/// @return sqrtP current pool sqrt price
/// @return currentTick current pool tick
/// @return nextTick next tick to calculate data
function _getInitialSwapData(
IKyberElasticPool pool,
bool willUpTick
) internal view returns (uint128 baseL, uint128 reinvestL, uint160 sqrtP, int24 currentTick, int24 nextTick) {
(sqrtP, currentTick, nextTick, ) = pool.getPoolState();
(baseL, reinvestL, ) = pool.getLiquidityState();
if (willUpTick) {
(, nextTick) = pool.initializedTicks(nextTick);
}
}
/// @inheritdoc MultiQuoter
function multiswap(
address p,
bool zeroForOne,
int256[] memory amounts
) internal view override returns (MultiSwapResult memory result) {
result.gasEstimates = new uint256[](amounts.length);
result.amounts0 = new int256[](amounts.length);
result.amounts1 = new int256[](amounts.length);
uint256 gasBefore = gasleft();
IKyberElasticPool pool = IKyberElasticPool(p);
SwapData memory swapData;
swapData.specifiedAmount = amounts[0];
swapData.zeroForOne = zeroForOne;
swapData.isExactInput = swapData.specifiedAmount > 0;
// tick (token1Qty/token0Qty) will increase for swapping from token1 to token0
bool willUpTick = (swapData.isExactInput != zeroForOne);
uint160 limitSqrtP = willUpTick ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1;
(
swapData.baseL,
swapData.reinvestL,
swapData.sqrtP,
swapData.currentTick,
swapData.nextTick
) = _getInitialSwapData(pool, willUpTick);
swapData.amountsIndex = 0;
swapData.gasAggregate = 0;
swapData.gasResidual = 0;
// verify limitSqrtP
if (willUpTick) {
require(limitSqrtP > swapData.sqrtP && limitSqrtP < TickMath.MAX_SQRT_RATIO, "bad limitSqrtP");
} else {
require(limitSqrtP < swapData.sqrtP && limitSqrtP > TickMath.MIN_SQRT_RATIO, "bad limitSqrtP");
}
uint24 swapFeeUnits = pool.swapFeeUnits();
// continue swapping while specified input/output isn't satisfied or price limit not reached
while (swapData.specifiedAmount != 0 && swapData.sqrtP != limitSqrtP) {
// math calculations work with the assumption that the price diff is capped to 5%
// since tick distance is uncapped between currentTick and nextTick
// we use tempNextTick to satisfy our assumption with MAX_TICK_DISTANCE is set to be matched this condition
int24 tempNextTick = swapData.nextTick;
if (willUpTick && tempNextTick > C.MAX_TICK_DISTANCE + swapData.currentTick) {
tempNextTick = swapData.currentTick + C.MAX_TICK_DISTANCE;
} else if (!willUpTick && tempNextTick < swapData.currentTick - C.MAX_TICK_DISTANCE) {
tempNextTick = swapData.currentTick - C.MAX_TICK_DISTANCE;
}
swapData.nextSqrtP = TickMath.getSqrtRatioAtTick(tempNextTick);
{
uint160 targetSqrtP = swapData.nextSqrtP;
// ensure next sqrtP (and its corresponding tick) does not exceed price limit
if (willUpTick == (swapData.nextSqrtP > limitSqrtP)) {
targetSqrtP = limitSqrtP;
}
int256 usedAmount;
int256 returnedAmount;
uint256 deltaL;
// local scope for targetSqrtP, usedAmount, returnedAmount and deltaL
(usedAmount, returnedAmount, deltaL, swapData.sqrtP) = SwapMath.computeSwapStep(
swapData.baseL + swapData.reinvestL,
swapData.sqrtP,
targetSqrtP,
swapFeeUnits,
swapData.specifiedAmount,
swapData.isExactInput,
swapData.zeroForOne
);
swapData.specifiedAmount -= usedAmount;
swapData.returnedAmount += returnedAmount;
swapData.reinvestL += deltaL.toUint128();
}
// if price has not reached the next sqrt price (still liquidity in current tick)
if (swapData.sqrtP != swapData.nextSqrtP) {
swapData.currentTick = TickMath.getTickAtSqrtRatio(swapData.sqrtP);
// use max since we want to over estimate
swapData.gasResidual = max(swapData.gasResidual, gasBefore - gasleft());
gasBefore = gasleft();
} else {
gasBefore = gasleft();
swapData.currentTick = willUpTick ? tempNextTick : tempNextTick - 1;
if (tempNextTick == swapData.nextTick) {
(swapData.baseL, swapData.nextTick) = _updateLiquidityAndCrossTick(
pool,
swapData.nextTick,
swapData.baseL,
willUpTick
);
}
swapData.gasAggregate += (gasBefore - gasleft() + swapData.gasResidual);
swapData.gasResidual = 0;
gasBefore = gasleft();
}
if (swapData.specifiedAmount == 0) {
(result.amounts0[swapData.amountsIndex], result.amounts1[swapData.amountsIndex]) = zeroForOne ==
swapData.isExactInput
? (amounts[swapData.amountsIndex], swapData.returnedAmount)
: (swapData.returnedAmount, amounts[swapData.amountsIndex]);
result.gasEstimates[swapData.amountsIndex] = swapData.gasAggregate + swapData.gasResidual;
if (swapData.amountsIndex == amounts.length - 1) {
return (result);
}
swapData.amountsIndex += 1;
swapData.specifiedAmount = amounts[swapData.amountsIndex].sub(amounts[swapData.amountsIndex - 1]);
}
}
for (uint256 i = swapData.amountsIndex; i < amounts.length; ++i) {
(result.amounts0[i], result.amounts1[i]) = zeroForOne == swapData.isExactInput
? (amounts[i], swapData.returnedAmount)
: (swapData.returnedAmount, amounts[i]);
result.gasEstimates[i] = swapData.gasAggregate + swapData.gasResidual;
}
}
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/// @dev Update liquidity net data and do cross tick
function _updateLiquidityAndCrossTick(
IKyberElasticPool pool,
int24 nextTick,
uint128 currentLiquidity,
bool willUpTick
) internal view returns (uint128 newLiquidity, int24 newNextTick) {
(, int128 liquidityNet, , ) = pool.ticks(nextTick);
if (willUpTick) {
(, newNextTick) = pool.initializedTicks(nextTick);
} else {
(newNextTick, ) = pool.initializedTicks(nextTick);
liquidityNet = -liquidityNet;
}
newLiquidity = LiqDeltaMath.applyLiquidityDelta(
currentLiquidity,
liquidityNet >= 0 ? uint128(liquidityNet) : liquidityNet.revToUint128(),
liquidityNet >= 0
);
}
/// @inheritdoc MultiQuoter
function pathHasMultiplePools(bytes memory path) internal pure override returns (bool) {
return path.hasMultiplePools();
}
/// @inheritdoc MultiQuoter
function pathSkipToken(bytes memory path) internal pure override returns (bytes memory) {
return path.skipToken();
}
}

View File

@ -0,0 +1,143 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity >=0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IMultiQuoter.sol";
import "./interfaces/IKyberElastic.sol";
import "./KyberElasticCommon.sol";
contract KyberElasticSampler is KyberElasticCommon {
uint256 private constant SAMPLING_GAS_LIMIT = 1500e3;
/// @dev Sample sell quotes from KyberElastic.
/// @param factory KyberElastic factory contract.
/// @param path Token route. Should be takerToken -> makerToken (at most two hops).
/// @param inputAmounts Taker token sell amount for each sample.
/// @return paths The encoded KyberElastic path for each sample.
/// @return gasEstimates Estimated amount of gas used
/// @return outputAmounts Maker amounts bought at each taker token amount.
function sampleSellsFromKyberElastic(
IMultiQuoter quoter,
address factory,
address[] memory path,
uint256[] memory inputAmounts
) public view returns (bytes[] memory paths, uint256[] memory gasEstimates, uint256[] memory outputAmounts) {
outputAmounts = new uint256[](inputAmounts.length);
paths = new bytes[](inputAmounts.length);
gasEstimates = new uint256[](inputAmounts.length);
address[][] memory poolPaths = _getPoolPaths(factory, path, inputAmounts[inputAmounts.length - 1]);
for (uint256 i = 0; i < poolPaths.length; ++i) {
if (!isValidPoolPath(poolPaths[i])) {
continue;
}
bytes memory dexPath = _toPath(path, poolPaths[i]);
uint256[] memory amountsOut;
uint256[] memory gasEstimatesTemp;
try quoter.quoteExactMultiInput{gas: SAMPLING_GAS_LIMIT}(factory, dexPath, inputAmounts) {} catch (
bytes memory reason
) {
bool success;
(success, amountsOut, gasEstimatesTemp) = decodeMultiSwapRevert(reason);
if (!success) {
continue;
}
for (uint256 j = 0; j < amountsOut.length; ++j) {
if (amountsOut[j] == 0) {
break;
}
if (outputAmounts[j] < amountsOut[j]) {
outputAmounts[j] = amountsOut[j];
paths[j] = dexPath;
gasEstimates[j] = gasEstimatesTemp[j];
} else if (outputAmounts[j] == amountsOut[j] && gasEstimates[j] > gasEstimatesTemp[j]) {
paths[j] = dexPath;
gasEstimates[j] = gasEstimatesTemp[j];
}
}
}
}
}
/// @dev Sample buy quotes from KyberElastic.
/// @param factory KyberElastic factory contract.
/// @param path Token route. Should be takerToken -> makerToken (at most two hops).
/// @param inputAmounts Maker token buy amount for each sample.
/// @return paths The encoded KyberElastic path for each sample.
/// @return gasEstimates Estimated amount of gas used
/// @return outputAmounts Taker amounts sold at each maker token amount.
function sampleBuysFromKyberElastic(
IMultiQuoter quoter,
address factory,
address[] memory path,
uint256[] memory inputAmounts
) public view returns (bytes[] memory paths, uint256[] memory gasEstimates, uint256[] memory outputAmounts) {
outputAmounts = new uint256[](inputAmounts.length);
paths = new bytes[](inputAmounts.length);
gasEstimates = new uint256[](inputAmounts.length);
address[] memory reversedPath = reverseAddressPath(path);
address[][] memory poolPaths = _getPoolPaths(factory, reversedPath, inputAmounts[inputAmounts.length - 1]);
for (uint256 i = 0; i < poolPaths.length; ++i) {
if (!isValidPoolPath(poolPaths[i])) {
continue;
}
bytes memory poolPath = _toPath(reversedPath, poolPaths[i]);
uint256[] memory amountsIn;
uint256[] memory gasEstimatesTemp;
try quoter.quoteExactMultiOutput{gas: SAMPLING_GAS_LIMIT}(factory, poolPath, inputAmounts) {} catch (
bytes memory reason
) {
bool success;
(success, amountsIn, gasEstimatesTemp) = decodeMultiSwapRevert(reason);
if (!success) {
continue;
}
for (uint256 j = 0; j < amountsIn.length; ++j) {
if (amountsIn[j] == 0) {
break;
}
if (outputAmounts[j] == 0 || outputAmounts[j] > amountsIn[j]) {
outputAmounts[j] = amountsIn[j];
paths[j] = _toPath(path, reverseAddressPath(poolPaths[i]));
gasEstimates[j] = gasEstimatesTemp[j];
} else if (outputAmounts[j] == amountsIn[j] && outputAmounts[j] > gasEstimatesTemp[j]) {
paths[j] = _toPath(path, reverseAddressPath(poolPaths[i]));
gasEstimates[j] = gasEstimatesTemp[j];
}
}
}
}
}
}

View File

@ -0,0 +1,110 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./SamplerUtils.sol";
interface IWstETH {
function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256);
function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256);
}
contract LidoSampler is SamplerUtils {
struct LidoInfo {
address stEthToken;
address wethToken;
address wstEthToken;
}
/// @dev Sample sell quotes from Lido
/// @param lidoInfo Info regarding a specific Lido deployment
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromLido(
LidoInfo memory lidoInfo,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory) {
_assertValidPair(makerToken, takerToken);
if (takerToken == lidoInfo.wethToken && makerToken == address(lidoInfo.stEthToken)) {
// Minting stETH is always 1:1 therefore we can just return the same amounts back.
return takerTokenAmounts;
}
return _sampleSellsForWrapped(lidoInfo, takerToken, makerToken, takerTokenAmounts);
}
/// @dev Sample buy quotes from Lido.
/// @param lidoInfo Info regarding a specific Lido deployment
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromLido(
LidoInfo memory lidoInfo,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory) {
if (takerToken == lidoInfo.wethToken && makerToken == address(lidoInfo.stEthToken)) {
// Minting stETH is always 1:1 therefore we can just return the same amounts back.
return makerTokenAmounts;
}
// Swap out `makerToken` and `takerToken` and re-use `_sampleSellsForWrapped`.
return _sampleSellsForWrapped(lidoInfo, makerToken, takerToken, makerTokenAmounts);
}
function _sampleSellsForWrapped(
LidoInfo memory lidoInfo,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) private view returns (uint256[] memory) {
IWstETH wstETH = IWstETH(lidoInfo.wstEthToken);
uint256 numSamples = takerTokenAmounts.length;
uint256[] memory makerTokenAmounts = new uint256[](numSamples);
if (takerToken == lidoInfo.stEthToken && makerToken == lidoInfo.wstEthToken) {
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = wstETH.getWstETHByStETH(takerTokenAmounts[i]);
}
return makerTokenAmounts;
}
if (takerToken == lidoInfo.wstEthToken && makerToken == lidoInfo.stEthToken) {
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = wstETH.getStETHByWstETH(takerTokenAmounts[i]);
}
return makerTokenAmounts;
}
// Returns 0 values.
return makerTokenAmounts;
}
}

View File

@ -0,0 +1,105 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IMStable.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract MStableSampler is SamplerUtils, ApproximateBuys {
/// @dev Default gas limit for mStable calls.
uint256 private constant DEFAULT_CALL_GAS = 800e3; // 800k
/// @dev Sample sell quotes from the mStable contract
/// @param router Address of the mStable contract
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromMStable(
address router,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
// Initialize array of maker token amounts.
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
try
IMStable(router).getSwapOutput{gas: DEFAULT_CALL_GAS}(takerToken, makerToken, takerTokenAmounts[i])
returns (uint256 amount) {
makerTokenAmounts[i] = amount;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
/// @dev Sample buy quotes from MStable contract
/// @param router Address of the mStable contract
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromMStable(
address router,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
return
_sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken, router),
takerTokenData: abi.encode(takerToken, router),
getSellQuoteCallback: _sampleSellForApproximateBuyFromMStable
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromMStable(
bytes memory takerTokenData,
bytes memory makerTokenData,
uint256 sellAmount
) private view returns (uint256 buyAmount) {
(address takerToken, address router) = abi.decode(takerTokenData, (address, address));
address makerToken = abi.decode(makerTokenData, (address));
try this.sampleSellsFromMStable(router, takerToken, makerToken, _toSingleValueArray(sellAmount)) returns (
uint256[] memory amounts
) {
return amounts[0];
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
return 0;
}
}
}

View File

@ -0,0 +1,255 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./SamplerUtils.sol";
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
interface IPSM {
// @dev Get the fee for selling USDC to DAI in PSM
// @return tin toll in [wad]
function tin() external view returns (uint256);
// @dev Get the fee for selling DAI to USDC in PSM
// @return tout toll out [wad]
function tout() external view returns (uint256);
// @dev Get the address of the PSM state Vat
// @return address of the Vat
function vat() external view returns (address);
// @dev Get the address of the underlying vault powering PSM
// @return address of gemJoin contract
function gemJoin() external view returns (address);
// @dev Get the address of DAI
// @return address of DAI contract
function dai() external view returns (address);
// @dev Sell USDC for DAI
// @param usr The address of the account trading USDC for DAI.
// @param gemAmt The amount of USDC to sell in USDC base units
function sellGem(address usr, uint256 gemAmt) external;
// @dev Buy USDC for DAI
// @param usr The address of the account trading DAI for USDC
// @param gemAmt The amount of USDC to buy in USDC base units
function buyGem(address usr, uint256 gemAmt) external;
}
interface IVAT {
// @dev Get a collateral type by identifier
// @param ilkIdentifier bytes32 identifier. Example: ethers.utils.formatBytes32String("PSM-USDC-A")
// @return ilk
// @return ilk.Art Total Normalised Debt in wad
// @return ilk.rate Accumulated Rates in ray
// @return ilk.spot Price with Safety Margin in ray
// @return ilk.line Debt Ceiling in rad
// @return ilk.dust Urn Debt Floor in rad
function ilks(
bytes32 ilkIdentifier
) external view returns (uint256 Art, uint256 rate, uint256 spot, uint256 line, uint256 dust);
}
contract MakerPSMSampler is SamplerUtils {
using LibSafeMathV06 for uint256;
/// @dev Information about which PSM module to use
struct MakerPsmInfo {
address psmAddress;
bytes32 ilkIdentifier;
address gemTokenAddress;
}
/// @dev Gas limit for MakerPsm calls.
uint256 private constant MAKER_PSM_CALL_GAS = 300e3; // 300k
// Maker units
// wad: fixed point decimal with 18 decimals (for basic quantities, e.g. balances)
uint256 private constant WAD = 10 ** 18;
// ray: fixed point decimal with 27 decimals (for precise quantites, e.g. ratios)
uint256 private constant RAY = 10 ** 27;
// rad: fixed point decimal with 45 decimals (result of integer multiplication with a wad and a ray)
uint256 private constant RAD = 10 ** 45;
// See https://github.com/makerdao/dss/blob/master/DEVELOPING.m
/// @dev Sample sell quotes from Maker PSM
function sampleSellsFromMakerPsm(
MakerPsmInfo memory psmInfo,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
IPSM psm = IPSM(psmInfo.psmAddress);
IVAT vat = IVAT(psm.vat());
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
if (makerToken != psm.dai() && takerToken != psm.dai()) {
return makerTokenAmounts;
}
for (uint256 i = 0; i < numSamples; i++) {
uint256 buyAmount = _samplePSMSell(psmInfo, makerToken, takerToken, takerTokenAmounts[i], psm, vat);
if (buyAmount == 0) {
break;
}
makerTokenAmounts[i] = buyAmount;
}
}
function sampleBuysFromMakerPsm(
MakerPsmInfo memory psmInfo,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
IPSM psm = IPSM(psmInfo.psmAddress);
IVAT vat = IVAT(psm.vat());
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
if (makerToken != psm.dai() && takerToken != psm.dai()) {
return takerTokenAmounts;
}
for (uint256 i = 0; i < numSamples; i++) {
uint256 sellAmount = _samplePSMBuy(psmInfo, makerToken, takerToken, makerTokenAmounts[i], psm, vat);
if (sellAmount == 0) {
break;
}
takerTokenAmounts[i] = sellAmount;
}
}
function _samplePSMSell(
MakerPsmInfo memory psmInfo,
address makerToken,
address takerToken,
uint256 takerTokenAmount,
IPSM psm,
IVAT vat
) private view returns (uint256) {
(uint256 totalDebtInWad, , , uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(
psmInfo.ilkIdentifier
);
uint256 gemTokenBaseUnit = uint256(1e6);
if (takerToken == psmInfo.gemTokenAddress) {
// Simulate sellGem
// Selling USDC to the PSM, increasing the total debt
// Convert USDC 6 decimals to 18 decimals [wad]
uint256 takerTokenAmountInWad = takerTokenAmount.safeMul(1e12);
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
// PSM is too full to fit
if (newTotalDebtInRad >= debtCeilingInRad) {
return 0;
}
uint256 feeInWad = takerTokenAmountInWad.safeMul(psm.tin()).safeDiv(WAD);
uint256 makerTokenAmountInWad = takerTokenAmountInWad.safeSub(feeInWad);
return makerTokenAmountInWad;
} else if (makerToken == psmInfo.gemTokenAddress) {
// Simulate buyGem
// Buying USDC from the PSM, decreasing the total debt
// Selling DAI for USDC, already in 18 decimals [wad]
uint256 takerTokenAmountInWad = takerTokenAmount;
if (takerTokenAmountInWad > totalDebtInWad) {
return 0;
}
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
// PSM is empty, not enough USDC to buy from it
if (newTotalDebtInRad <= debtFloorInRad) {
return 0;
}
uint256 feeDivisorInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
uint256 makerTokenAmountInGemTokenBaseUnits = takerTokenAmountInWad.safeMul(gemTokenBaseUnit).safeDiv(
feeDivisorInWad
);
return makerTokenAmountInGemTokenBaseUnits;
}
return 0;
}
function _samplePSMBuy(
MakerPsmInfo memory psmInfo,
address makerToken,
address takerToken,
uint256 makerTokenAmount,
IPSM psm,
IVAT vat
) private view returns (uint256) {
(uint256 totalDebtInWad, , , uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(
psmInfo.ilkIdentifier
);
if (takerToken == psmInfo.gemTokenAddress) {
// Simulate sellGem
// Selling USDC to the PSM, increasing the total debt
uint256 makerTokenAmountInWad = makerTokenAmount;
uint256 feeDivisorInWad = WAD.safeSub(psm.tin()); // eg. 0.999 * 10 ** 18 with 0.1% tin;
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(WAD).safeDiv(feeDivisorInWad);
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
// PSM is too full to fit
if (newTotalDebtInRad >= debtCeilingInRad) {
return 0;
}
uint256 takerTokenAmountInGemInGemBaseUnits = (takerTokenAmountInWad.safeDiv(1e12)).safeAdd(1); // Add 1 to deal with cut off decimals converting to lower decimals
return takerTokenAmountInGemInGemBaseUnits;
} else if (makerToken == psmInfo.gemTokenAddress) {
// Simulate buyGem
// Buying USDC from the PSM, decreasing the total debt
uint256 makerTokenAmountInWad = makerTokenAmount.safeMul(1e12);
uint256 feeMultiplierInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(feeMultiplierInWad).safeDiv(WAD);
if (takerTokenAmountInWad > totalDebtInWad) {
return 0;
}
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
// PSM is empty, not enough USDC to buy
if (newTotalDebtInRad <= debtFloorInRad) {
return 0;
}
return takerTokenAmountInWad;
}
return 0;
}
}

View File

@ -0,0 +1,135 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IMooniswap.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract MooniswapSampler is SamplerUtils, ApproximateBuys {
/// @dev Gas limit for Mooniswap calls.
uint256 private constant MOONISWAP_CALL_GAS = 150e3; // 150k
/// @dev Sample sell quotes from Mooniswap.
/// @param registry Address of the Mooniswap Registry.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return pool The contract address for the pool
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromMooniswap(
address registry,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (IMooniswap pool, uint256[] memory makerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
uint256 buyAmount = sampleSingleSellFromMooniswapPool(
registry,
takerToken,
makerToken,
takerTokenAmounts[i]
);
makerTokenAmounts[i] = buyAmount;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
}
pool = IMooniswap(IMooniswapRegistry(registry).pools(takerToken, makerToken));
}
function sampleSingleSellFromMooniswapPool(
address registry,
address mooniswapTakerToken,
address mooniswapMakerToken,
uint256 takerTokenAmount
) public view returns (uint256) {
// Find the pool for the pair.
IMooniswap pool = IMooniswap(IMooniswapRegistry(registry).pools(mooniswapTakerToken, mooniswapMakerToken));
// If there is no pool then return early
if (address(pool) == address(0)) {
return 0;
}
uint256 poolBalance = mooniswapTakerToken == address(0)
? address(pool).balance
: IERC20TokenV06(mooniswapTakerToken).balanceOf(address(pool));
// If the pool balance is smaller than the sell amount
// don't sample to avoid multiplication overflow in buys
if (poolBalance < takerTokenAmount) {
return 0;
}
try
pool.getReturn{gas: MOONISWAP_CALL_GAS}(mooniswapTakerToken, mooniswapMakerToken, takerTokenAmount)
returns (uint256 amount) {
return amount;
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
return 0;
}
}
/// @dev Sample buy quotes from Mooniswap.
/// @param registry Address of the Mooniswap Registry.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @return pool The contract address for the pool
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromMooniswap(
address registry,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (IMooniswap pool, uint256[] memory takerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(registry, makerToken),
takerTokenData: abi.encode(registry, takerToken),
getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap
}),
makerTokenAmounts
);
pool = IMooniswap(IMooniswapRegistry(registry).pools(takerToken, makerToken));
}
function _sampleSellForApproximateBuyFromMooniswap(
bytes memory takerTokenData,
bytes memory makerTokenData,
uint256 sellAmount
) private view returns (uint256 buyAmount) {
(address registry, address mooniswapTakerToken) = abi.decode(takerTokenData, (address, address));
(address _registry, address mooniswapMakerToken) = abi.decode(makerTokenData, (address, address));
return sampleSingleSellFromMooniswapPool(registry, mooniswapTakerToken, mooniswapMakerToken, sellAmount);
}
}

View File

@ -0,0 +1,180 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity >=0.7 <=0.8;
pragma experimental ABIEncoderV2;
import "./interfaces/IMultiQuoter.sol";
/// @title Provides quotes for multiple swap amounts
/// @notice Allows getting the expected amount out or amount in for multiple given swap amounts without executing the swap
abstract contract MultiQuoter is IMultiQuoter {
// TODO: both quoteExactMultiInput and quoteExactMultiOutput revert at the end of the quoting logic
// and return results encodied into a revert reason. The revert should be removed and replaced with
// a normal return statement whenever the Tick Based AMM samplers stop having the two pool filtering
// logic. The two pool filtering logic causes pool's storage slots to be warmed up, causing gas estimates
// to be significantly below the gas used during settlement. Additionally, per the following EIP
// this revert logic might not clear warm storage slots in the future: https://eips.ethereum.org/EIPS/eip-3978
// the result of multiswap
struct MultiSwapResult {
// the gas estimate for each of swap amounts
uint256[] gasEstimates;
// the token0 delta for each swap amount, positive indicates sent and negative indicates receipt
int256[] amounts0;
// the token1 delta for each swap amount, positive indicates sent and negative indicates receipt
int256[] amounts1;
}
// @inheritdoc IMultiQuoter
function quoteExactMultiInput(
address factory,
bytes memory path,
uint256[] memory amountsIn
) external view override {
for (uint256 i = 0; i < amountsIn.length - 1; ++i) {
require(amountsIn[i] <= amountsIn[i + 1], "MultiQuoter/amountsIn must be monotonically increasing");
}
uint256[] memory gasEstimates = new uint256[](amountsIn.length);
while (true) {
(address pool, bool zeroForOne) = getFirstSwapDetails(factory, path, true);
// multiswap only accepts int256[] for input amounts
int256[] memory amounts = new int256[](amountsIn.length);
for (uint256 i = 0; i < amountsIn.length; ++i) {
amounts[i] = int256(amountsIn[i]);
}
MultiSwapResult memory result = multiswap(pool, zeroForOne, amounts);
for (uint256 i = 0; i < amountsIn.length; ++i) {
amountsIn[i] = zeroForOne ? uint256(-result.amounts1[i]) : uint256(-result.amounts0[i]);
gasEstimates[i] += result.gasEstimates[i];
}
// decide whether to continue or terminate
if (pathHasMultiplePools(path)) {
path = pathSkipToken(path);
} else {
// quote results must be encoded into a revert because otherwise subsequent calls
// to MultiQuoter result in multiswap hitting pool storage slots that are
// already warm. This results in very inaccurate gas estimates when estimating gas
// usage for settlement.
revertWithAmountsAndGas(amountsIn, gasEstimates);
}
}
}
// @inheritdoc IMultiQuoter
function quoteExactMultiOutput(
address factory,
bytes memory path,
uint256[] memory amountsOut
) external view override {
uint256[] memory amountsIn = new uint256[](amountsOut.length);
uint256[] memory gasEstimates = new uint256[](amountsOut.length);
for (uint256 i = 0; i < amountsOut.length - 1; ++i) {
require(amountsOut[i] <= amountsOut[i + 1], "MultiQuoter/amountsOut must be monotonically increasing");
}
uint256 nextAmountsLength = amountsOut.length;
while (true) {
(address pool, bool zeroForOne) = getFirstSwapDetails(factory, path, false);
// multiswap only accepts int256[] for output amounts
int256[] memory amounts = new int256[](nextAmountsLength);
for (uint256 i = 0; i < nextAmountsLength; ++i) {
amounts[i] = -int256(amountsOut[i]);
}
MultiSwapResult memory result = multiswap(pool, zeroForOne, amounts);
for (uint256 i = 0; i < nextAmountsLength; ++i) {
uint256 amountReceived = zeroForOne ? uint256(-result.amounts1[i]) : uint256(-result.amounts0[i]);
if (amountReceived != amountsOut[i]) {
// for exact output swaps we need to check whether we would receive the full amount due to
// multiswap behavior when hitting the limit
nextAmountsLength = i;
break;
} else {
// populate amountsOut for the next pool
amountsOut[i] = zeroForOne ? uint256(result.amounts0[i]) : uint256(result.amounts1[i]);
gasEstimates[i] += result.gasEstimates[i];
}
}
if (nextAmountsLength == 0 || !pathHasMultiplePools(path)) {
for (uint256 i = 0; i < nextAmountsLength; ++i) {
amountsIn[i] = amountsOut[i];
}
// quote results must be encoded into a revert because otherwise subsequent calls
// to MultiQuoter result in multiswap hitting pool storage slots that are
// already warm. This results in very inaccurate gas estimates when estimating gas
// usage for settlement.
revertWithAmountsAndGas(amountsIn, gasEstimates);
}
path = pathSkipToken(path);
}
}
/// @notice reverts the transaction while encoding amounts and gas estimates into the revert reason
/// @param amounts the amounts to encode as the first encoding in the revert reason
/// @param gasEstimates the gas estimates to encode as the second encoding in the revert reason
function revertWithAmountsAndGas(uint256[] memory amounts, uint256[] memory gasEstimates) private pure {
bytes memory revertResult = abi.encodeWithSignature("result(uint256[],uint256[])", amounts, gasEstimates);
assembly {
revert(add(revertResult, 0x20), mload(revertResult))
}
}
/// @notice get the details of the first swap including the pool to swap within and direction of the swap
/// @param factory The factory contract managing the tick based AMM pools
/// @param path The path of the swap as a combination of token pairs and/or pool fee
/// @param isExactInput whether or not this wap is an exact input or exact output swap (this determines whether tokenIn is first or second in the path)
/// @return pool The first pool derived from the path.
/// @return zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
function getFirstSwapDetails(
address factory,
bytes memory path,
bool isExactInput
) internal view virtual returns (address pool, bool zeroForOne);
/// @notice swap multiple amounts of token0 for token1 or token1 for token1
/// @dev The results of multiswap includes slight rounding issues resulting from rounding up/rounding down in SqrtPriceMath library. Additionally,
/// it should be noted that multiswap requires a monotonically increasing list of amounts for exact inputs and monotonically decreasing list of
/// amounts for exact outputs.
/// @param pool The tick based AMM pool to simulate each of the swap amounts for
/// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
/// @param amounts The amounts of the swaps, positive values indicate exactInput and negative values indicate exact output
/// @return result The results of the swap as a MultiSwapResult struct with gas used, token0 and token1 deltas
function multiswap(
address pool,
bool zeroForOne,
int256[] memory amounts
) internal view virtual returns (MultiSwapResult memory result);
/// @notice Returns true iff the path contains two or more pools
/// @param path The encoded swap path
/// @return True if path contains two or more pools, otherwise false
function pathHasMultiplePools(bytes memory path) internal pure virtual returns (bool);
/// @notice Skips a token and/or fee element from the path and returns the remainder
/// @param path The swap path
/// @return The remaining token + fee elements in the path
function pathSkipToken(bytes memory path) internal pure virtual returns (bytes memory);
}

View File

@ -0,0 +1,201 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
interface IExchange {
enum OrderStatus {
INVALID,
FILLABLE,
FILLED,
CANCELLED,
EXPIRED
}
/// @dev A standard OTC or OO limit order.
struct LimitOrder {
IERC20TokenV06 makerToken;
IERC20TokenV06 takerToken;
uint128 makerAmount;
uint128 takerAmount;
uint128 takerTokenFeeAmount;
address maker;
address taker;
address sender;
address feeRecipient;
bytes32 pool;
uint64 expiry;
uint256 salt;
}
/// @dev An RFQ limit order.
struct RfqOrder {
IERC20TokenV06 makerToken;
IERC20TokenV06 takerToken;
uint128 makerAmount;
uint128 takerAmount;
address maker;
address taker;
address txOrigin;
bytes32 pool;
uint64 expiry;
uint256 salt;
}
/// @dev Info on a limit or RFQ order.
struct OrderInfo {
bytes32 orderHash;
OrderStatus status;
uint128 takerTokenFilledAmount;
}
/// @dev Allowed signature types.
enum SignatureType {
ILLEGAL,
INVALID,
EIP712,
ETHSIGN
}
/// @dev Encoded EC signature.
struct Signature {
// How to validate the signature.
SignatureType signatureType;
// EC Signature data.
uint8 v;
// EC Signature data.
bytes32 r;
// EC Signature data.
bytes32 s;
}
/// @dev Get the order info for a limit order.
/// @param order The limit order.
/// @return orderInfo Info about the order.
function getLimitOrderInfo(LimitOrder memory order) external view returns (OrderInfo memory orderInfo);
/// @dev Get order info, fillable amount, and signature validity for a limit order.
/// Fillable amount is determined using balances and allowances of the maker.
/// @param order The limit order.
/// @param signature The order signature.
/// @return orderInfo Info about the order.
/// @return actualFillableTakerTokenAmount How much of the order is fillable
/// based on maker funds, in taker tokens.
/// @return isSignatureValid Whether the signature is valid.
function getLimitOrderRelevantState(
LimitOrder memory order,
Signature calldata signature
) external view returns (OrderInfo memory orderInfo, uint128 actualFillableTakerTokenAmount, bool isSignatureValid);
}
contract NativeOrderSampler {
using LibSafeMathV06 for uint256;
using LibBytesV06 for bytes;
/// @dev Gas limit for calls to `getOrderFillableTakerAmount()`.
uint256 internal constant DEFAULT_CALL_GAS = 200e3; // 200k
/// @dev Queries the fillable taker asset amounts of native orders.
/// Effectively ignores orders that have empty signatures or
/// maker/taker asset amounts (returning 0).
/// @param orders Native limit orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param exchange The V4 exchange.
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
/// by each order in `orders`.
function getLimitOrderFillableTakerAssetAmounts(
IExchange.LimitOrder[] memory orders,
IExchange.Signature[] memory orderSignatures,
IExchange exchange
) public view returns (uint256[] memory orderFillableTakerAssetAmounts) {
orderFillableTakerAssetAmounts = new uint256[](orders.length);
for (uint256 i = 0; i != orders.length; i++) {
try
this.getLimitOrderFillableTakerAmount{gas: DEFAULT_CALL_GAS}(orders[i], orderSignatures[i], exchange)
returns (uint256 amount) {
orderFillableTakerAssetAmounts[i] = amount;
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
orderFillableTakerAssetAmounts[i] = 0;
}
}
}
/// @dev Queries the fillable taker asset amounts of native orders.
/// Effectively ignores orders that have empty signatures or
/// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`.
/// @param exchange The V4 exchange.
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
/// by each order in `orders`.
function getLimitOrderFillableMakerAssetAmounts(
IExchange.LimitOrder[] memory orders,
IExchange.Signature[] memory orderSignatures,
IExchange exchange
) public view returns (uint256[] memory orderFillableMakerAssetAmounts) {
orderFillableMakerAssetAmounts = getLimitOrderFillableTakerAssetAmounts(orders, orderSignatures, exchange);
// `orderFillableMakerAssetAmounts` now holds taker asset amounts, so
// convert them to maker asset amounts.
for (uint256 i = 0; i < orders.length; ++i) {
if (orderFillableMakerAssetAmounts[i] != 0) {
orderFillableMakerAssetAmounts[i] = LibMathV06.getPartialAmountCeil(
orderFillableMakerAssetAmounts[i],
orders[i].takerAmount,
orders[i].makerAmount
);
}
}
}
/// @dev Get the fillable taker amount of an order, taking into account
/// order state, maker fees, and maker balances.
function getLimitOrderFillableTakerAmount(
IExchange.LimitOrder memory order,
IExchange.Signature memory signature,
IExchange exchange
) public view virtual returns (uint256 fillableTakerAmount) {
if (
signature.signatureType == IExchange.SignatureType.ILLEGAL ||
signature.signatureType == IExchange.SignatureType.INVALID ||
order.makerAmount == 0 ||
order.takerAmount == 0
) {
return 0;
}
(IExchange.OrderInfo memory orderInfo, uint128 remainingFillableTakerAmount, bool isSignatureValid) = exchange
.getLimitOrderRelevantState(order, signature);
if (
orderInfo.status != IExchange.OrderStatus.FILLABLE ||
!isSignatureValid ||
order.makerToken == IERC20TokenV06(0)
) {
return 0;
}
fillableTakerAmount = uint256(remainingFillableTakerAmount);
}
}

View File

@ -0,0 +1,68 @@
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IPlatypus.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
contract PlatypusSampler is SamplerUtils, ApproximateBuys {
function sampleSellsFromPlatypus(
address pool,
address[] memory path,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
try IPlatypus(pool).quotePotentialSwap(path[0], path[1], takerTokenAmounts[i]) returns (
uint256 amountAfterFees,
uint256 feeAmount
) {
makerTokenAmounts[i] = amountAfterFees;
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory result) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
function sampleBuysFromPlatypus(
address pool,
address[] memory path,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
address[] memory invertBuyPath = new address[](2);
invertBuyPath[0] = path[1];
invertBuyPath[1] = path[0];
return
_sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(pool, invertBuyPath),
takerTokenData: abi.encode(pool, path),
getSellQuoteCallback: _sampleSellForApproximateBuyFromPlatypus
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromPlatypus(
bytes memory makerTokenData,
bytes memory takerTokenData,
uint256 sellAmount
) private view returns (uint256 buyAmount) {
(address _pool, address[] memory _path) = abi.decode(makerTokenData, (address, address[]));
(bool success, bytes memory resultData) = address(this).staticcall(
abi.encodeWithSelector(this.sampleSellsFromPlatypus.selector, _pool, _path, _toSingleValueArray(sellAmount))
);
if (!success) {
return 0;
}
// solhint-disable-next-line indent
return abi.decode(resultData, (uint256[]))[0];
}
}

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
contract SamplerUtils {
/// @dev Overridable way to get token decimals.
/// @param tokenAddress Address of the token.
/// @return decimals The decimal places for the token.
function _getTokenDecimals(address tokenAddress) internal view virtual returns (uint8 decimals) {
return LibERC20TokenV06.compatDecimals(IERC20TokenV06(tokenAddress));
}
function _toSingleValueArray(uint256 v) internal pure returns (uint256[] memory arr) {
arr = new uint256[](1);
arr[0] = v;
}
/// @dev Assert that the tokens in a trade pair are valid.
/// @param makerToken Address of the maker token.
/// @param takerToken Address of the taker token.
function _assertValidPair(address makerToken, address takerToken) internal pure {
require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
}
}

View File

@ -0,0 +1,105 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./ApproximateBuys.sol";
import "./interfaces/IShell.sol";
import "./SamplerUtils.sol";
contract ShellSampler is SamplerUtils, ApproximateBuys {
struct ShellInfo {
address poolAddress;
}
/// @dev Default gas limit for Shell calls.
uint256 private constant DEFAULT_CALL_GAS = 300e3; // 300k
/// @dev Sample sell quotes from the Shell pool contract
/// @param pool Address of the Shell pool contract
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromShell(
address pool,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
// Initialize array of maker token amounts.
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
try
IShell(pool).viewOriginSwap{gas: DEFAULT_CALL_GAS}(takerToken, makerToken, takerTokenAmounts[i])
returns (uint256 amount) {
makerTokenAmounts[i] = amount;
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
/// @dev Sample buy quotes from Shell pool contract
/// @param pool Address of the Shell pool contract
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromShell(
address pool,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
return
_sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken, pool),
takerTokenData: abi.encode(takerToken, pool),
getSellQuoteCallback: _sampleSellForApproximateBuyFromShell
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromShell(
bytes memory takerTokenData,
bytes memory makerTokenData,
uint256 sellAmount
) private view returns (uint256 buyAmount) {
(address takerToken, address pool) = abi.decode(takerTokenData, (address, address));
address makerToken = abi.decode(makerTokenData, (address));
try this.sampleSellsFromShell(pool, takerToken, makerToken, _toSingleValueArray(sellAmount)) returns (
uint256[] memory amounts
) {
return amounts[0];
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
return 0;
}
}
}

View File

@ -0,0 +1,133 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
interface IReadProxyAddressResolver {
function target() external view returns (address);
}
interface IAddressResolver {
function getAddress(bytes32 name) external view returns (address);
}
interface IExchanger {
// Ethereum Mainnet
function getAmountsForAtomicExchange(
uint256 sourceAmount,
bytes32 sourceCurrencyKey,
bytes32 destinationCurrencyKey
) external view returns (uint256 amountReceived, uint256 fee, uint256 exchangeFeeRate);
// Optimism
function getAmountsForExchange(
uint256 sourceAmount,
bytes32 sourceCurrencyKey,
bytes32 destinationCurrencyKey
) external view returns (uint256 amountReceived, uint256 fee, uint256 exchangeFeeRate);
}
contract SynthetixSampler {
/// @dev Sample sell quotes from Synthetix Atomic Swap.
/// @param takerTokenSymbol Symbol (currency key) of the taker token (what to sell).
/// @param makerTokenSymbol Symbol (currency key) of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order).
/// @return synthetix Synthetix address.
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
function sampleSellsFromSynthetix(
IReadProxyAddressResolver readProxy,
bytes32 takerTokenSymbol,
bytes32 makerTokenSymbol,
uint256[] memory takerTokenAmounts
) public view returns (address synthetix, uint256[] memory makerTokenAmounts) {
synthetix = getSynthetixAddress(readProxy);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
if (numSamples == 0) {
return (synthetix, makerTokenAmounts);
}
makerTokenAmounts[0] = exchange(readProxy, takerTokenAmounts[0], takerTokenSymbol, makerTokenSymbol);
// Synthetix atomic swap has a fixed rate. Calculate the rest based on the first value (and save gas).
for (uint256 i = 1; i < numSamples; i++) {
makerTokenAmounts[i] = (makerTokenAmounts[0] * takerTokenAmounts[i]) / takerTokenAmounts[0];
}
}
/// @dev Sample buy quotes from Synthetix Atomic Swap.
/// @param takerTokenSymbol Symbol (currency key) of the taker token (what to sell).
/// @param makerTokenSymbol Symbol (currency key) of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample (sorted in ascending order).
/// @return synthetix Synthetix address.
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
function sampleBuysFromSynthetix(
IReadProxyAddressResolver readProxy,
bytes32 takerTokenSymbol,
bytes32 makerTokenSymbol,
uint256[] memory makerTokenAmounts
) public view returns (address synthetix, uint256[] memory takerTokenAmounts) {
synthetix = getSynthetixAddress(readProxy);
// Since Synthetix atomic have a fixed rate, we can pick any reasonablely size takerTokenAmount (fixed to 1 ether here) and calculate the rest.
uint256 amountReceivedForEther = exchange(readProxy, 1 ether, takerTokenSymbol, makerTokenSymbol);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
takerTokenAmounts[i] = (1 ether * makerTokenAmounts[i]) / amountReceivedForEther;
}
}
function exchange(
IReadProxyAddressResolver readProxy,
uint256 sourceAmount,
bytes32 sourceCurrencyKey,
bytes32 destinationCurrencyKey
) private view returns (uint256 amountReceived) {
IExchanger exchanger = getExchanger(readProxy);
uint256 chainId;
assembly {
chainId := chainid()
}
if (chainId == 1) {
(amountReceived, , ) = exchanger.getAmountsForAtomicExchange(
sourceAmount,
sourceCurrencyKey,
destinationCurrencyKey
);
} else {
(amountReceived, , ) = exchanger.getAmountsForExchange(
sourceAmount,
sourceCurrencyKey,
destinationCurrencyKey
);
}
}
function getSynthetixAddress(IReadProxyAddressResolver readProxy) private view returns (address) {
return IAddressResolver(readProxy.target()).getAddress("Synthetix");
}
function getExchanger(IReadProxyAddressResolver readProxy) private view returns (IExchanger) {
return IExchanger(IAddressResolver(readProxy.target()).getAddress("Exchanger"));
}
}

View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity >=0.6;
pragma experimental ABIEncoderV2;
/// @title Provides common utility functions for Tick Based AMM sampling
contract TickBasedAMMCommon {
/// @dev return a reversed address path
function reverseAddressPath(address[] memory path) internal pure returns (address[] memory reversedPath) {
reversedPath = new address[](path.length);
for (uint256 i = 0; i < path.length; ++i) {
reversedPath[i] = path[path.length - i - 1];
}
}
function isValidPoolPath(address[] memory poolPath) internal pure returns (bool) {
for (uint256 i = 0; i < poolPath.length; i++) {
if (poolPath[i] == address(0)) {
return false;
}
}
return true;
}
/// @dev decode a MultiSwap revert reason into individual constituents of the MultiSwap result.
/// @param revertReason the encoded revert reason caught from MultiSwap call
/// @return success whether or not the revertReason was able to be successfully decoded
/// @return amounts the MultiSwap quoted amounts
/// @return gasEstimates the MultiSwap gas estimates corresponding to each of the quoted amounts
function decodeMultiSwapRevert(
bytes memory revertReason
) internal pure returns (bool success, uint256[] memory amounts, uint256[] memory gasEstimates) {
bytes4 selector;
assembly {
selector := mload(add(revertReason, 32))
}
if (selector != bytes4(keccak256("result(uint256[],uint256[])"))) {
return (false, amounts, gasEstimates);
}
assembly {
let length := sub(mload(revertReason), 4)
revertReason := add(revertReason, 4)
mstore(revertReason, length)
}
(amounts, gasEstimates) = abi.decode(revertReason, (uint256[], uint256[]));
return (true, amounts, gasEstimates);
}
}

View File

@ -0,0 +1,148 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity >=0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/ITraderJoeV2.sol";
import "./TickBasedAMMCommon.sol";
contract TraderJoeV2Common is TickBasedAMMCommon {
function toTraderJoeV2Path(
address[] memory tokenPath,
address[] memory poolPath
) internal view returns (bytes memory traderJoeV2Path) {
require(
tokenPath.length >= 2 && tokenPath.length == poolPath.length + 1,
"TraderJoeV2Common/invalid path lengths"
);
// TraderJoeV2 paths are tightly packed as:
// [token0, token0Token1PairBinStep, token1, token1Token2PairBinStep, token2, ...]
traderJoeV2Path = new bytes(tokenPath.length * 20 + poolPath.length * 2);
uint256 o;
assembly {
o := add(traderJoeV2Path, 32)
}
for (uint256 i = 0; i < tokenPath.length; ++i) {
if (i > 0) {
uint16 binStep = ITraderJoeV2Pool(poolPath[i - 1]).feeParameters().binStep;
assembly {
mstore(o, shl(240, binStep))
o := add(o, 2)
}
}
address token = tokenPath[i];
assembly {
mstore(o, shl(96, token))
o := add(o, 20)
}
}
}
function getBinStepsFromPoolPath(address[] memory poolPath) internal view returns (uint256[] memory binSteps) {
binSteps = new uint256[](poolPath.length);
for (uint256 i = 0; i < poolPath.length; ++i) {
binSteps[i] = ITraderJoeV2Pool(poolPath[i]).feeParameters().binStep;
}
}
/// @dev Returns a path of pools to sample against. The caller is responsible for not using path involving zero address(es)
function getTraderJoeV2PoolPaths(
address factory,
address[] memory path
) internal view returns (address[][] memory poolPaths) {
if (path.length == 2) {
return getTraderJoeV2PoolPathSingleHop(factory, path);
}
if (path.length == 3) {
return getTraderJoeV2PoolPathTwoHop(factory, path);
}
revert("TraderJoeV2Common/unsupported token path length");
}
function getTraderJoeV2PoolPathSingleHop(
address factory,
address[] memory path
) private view returns (address[][] memory poolPaths) {
poolPaths = new address[][](2);
address[2] memory topPools = getTopTwoPools(factory, path[0], path[1]);
for (uint256 i = 0; i < 2; i++) {
poolPaths[i] = new address[](1);
poolPaths[i][0] = topPools[i];
}
}
function getTraderJoeV2PoolPathTwoHop(
address factory,
address[] memory path
) private view returns (address[][] memory poolPaths) {
poolPaths = new address[][](4);
address[2] memory firstHopTopPools = getTopTwoPools(factory, path[0], path[1]);
address[2] memory secondHopTopPools = getTopTwoPools(factory, path[1], path[2]);
uint256 pathCount = 0;
for (uint256 i = 0; i < 2; i++) {
for (uint256 j = 0; j < 2; j++) {
poolPaths[i] = new address[](2);
address[] memory currentPath = poolPaths[pathCount];
currentPath[0] = firstHopTopPools[i];
currentPath[1] = secondHopTopPools[j];
pathCount++;
}
}
}
/// @dev Returns top 0-2 pools and corresponding output amounts based on swaping `inputAmount`.
/// Addresses in `topPools` can be zero addresses when there are pool isn't available.
function getTopTwoPools(
address factory,
address inputToken,
address outputToken
) private view returns (address[2] memory topPools) {
ITraderJoeV2Factory.PoolInformation[] memory availablePools = ITraderJoeV2Factory(factory).getAllLBPairs(
inputToken,
outputToken
);
uint256[2] memory topLiquidityAmounts;
for (uint256 i = 0; i < availablePools.length; ++i) {
if (availablePools[i].ignoredForRouting) {
continue;
}
ITraderJoeV2Pool pool = ITraderJoeV2Pool(availablePools[i].pool);
(uint256 pairReserveX, uint256 pairReserveY, ) = pool.getReservesAndId();
uint256 currLiquidity = pairReserveX + pairReserveY;
if (currLiquidity > topLiquidityAmounts[0]) {
topLiquidityAmounts[1] = topLiquidityAmounts[0];
topPools[1] = topPools[0];
topLiquidityAmounts[0] = currLiquidity;
topPools[0] = address(pool);
} else if (currLiquidity > topLiquidityAmounts[1]) {
topLiquidityAmounts[1] = currLiquidity;
topPools[1] = address(pool);
}
}
}
}

View File

@ -0,0 +1,304 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.8.10;
import "@traderjoe-xyz/joe-v2/src/libraries/FeeHelper.sol";
import "@traderjoe-xyz/joe-v2/src/libraries/SwapHelper.sol";
import "@traderjoe-xyz/joe-v2/src/libraries/Math512Bits.sol";
import "@traderjoe-xyz/joe-v2/src/libraries/Constants.sol";
import "@traderjoe-xyz/joe-v2/src/libraries/SafeCast.sol";
import "@traderjoe-xyz/joe-v2/src/interfaces/ILBPairFactory.sol";
import "@traderjoe-xyz/joe-v2/src/interfaces/ILBToken.sol";
import "@traderjoe-xyz/joe-v2/src/LBErrors.sol";
import "./libraries/TraderJoeV2Path.sol";
import "./MultiQuoter.sol";
contract TraderJoeV2MultiQuoter is MultiQuoter {
using TraderJoeV2Path for bytes;
using FeeHelper for FeeHelper.FeeParameters;
using SwapHelper for ILBPair.Bin;
using Math512Bits for uint256;
using SafeCast for uint256;
// the top level state of the multiswap, the results are transient from one swap calculation to the next
struct SwapState {
// the amount remaining to be swapped in/out of the input/output asset
uint256 amountSpecifiedRemaining;
// the amount already swapped out/in of the output/input asset
uint256 amountCalculated;
// the current quote amount we are querying liquidity for
uint256 amountsIndex;
// the gas we had left before we started the swap process
ILBPair.Bin bin;
// the current active id of the bin being queried
uint256 activeId;
// the top level fee parameters used for the swap
FeeHelper.FeeParameters fp;
// the total amount of tokenX in the pair reserves (only used for exact output swaps as a safety check)
uint256 pairReserveX;
// the total amount of tokenY in the pair reserves (only used for exact output swaps as a safety check)
uint256 pairReserveY;
// the current aggregate gas estimate for multiswap, only updated upon tick crossing
uint256 gasAggregate;
}
// the top level parameters used for the multiswap logic
struct SwapParams {
// whether the current swap is an exactInput swap
bool exactInput;
// whether this swap is from token0 --> token1
bool zeroForOne;
// the pool we are swapping on
address pool;
}
/// @inheritdoc MultiQuoter
function getFirstSwapDetails(
address factory,
bytes memory path,
bool isExactInput
) internal view override returns (address pool, bool zeroForOne) {
address tokenIn;
address tokenOut;
uint16 binStep;
if (isExactInput) {
(tokenIn, tokenOut, binStep) = path.decodeFirstPool();
} else {
(tokenOut, tokenIn, binStep) = path.decodeFirstPool();
}
pool = address(ILBFactory(factory).getLBPairInformation(IERC20(tokenIn), IERC20(tokenOut), binStep).LBPair);
zeroForOne = address(ILBPair(pool).tokenY()) == tokenOut;
}
/// @notice Returns the amounts in/out and fees of the bin based on the bin reserves
/// @param state the current state of the underlying pool
/// @param params helper params describing the multiswap conditions
/// @return amountInToBin the amount of tokenX/tokenY going into the current bin
/// @return amountOutOfBin the amount of tokenX/tokenY going out of the current bin
/// @return fees the total fees paid in input token resulting from this swap
function getAmountsAndUpdateBin(
SwapState memory state,
SwapParams memory params
) private view returns (uint256 amountInToBin, uint256 amountOutOfBin, FeeHelper.FeesDistribution memory fees) {
uint256 price = BinHelper.getPriceFromId(state.activeId, state.fp.binStep);
uint256 reserve;
uint256 maxAmountInToBin;
if (params.zeroForOne) {
reserve = state.bin.reserveY;
maxAmountInToBin = reserve.shiftDivRoundUp(Constants.SCALE_OFFSET, price);
} else {
reserve = state.bin.reserveX;
maxAmountInToBin = price.mulShiftRoundUp(reserve, Constants.SCALE_OFFSET);
}
state.fp.updateVolatilityAccumulated(state.activeId);
fees = state.fp.getFeeAmountDistribution(state.fp.getFeeAmount(maxAmountInToBin));
if (params.exactInput && maxAmountInToBin + fees.total <= state.amountSpecifiedRemaining) {
amountInToBin = maxAmountInToBin;
amountOutOfBin = reserve;
} else if (!params.exactInput && reserve <= state.amountSpecifiedRemaining) {
amountInToBin = maxAmountInToBin;
amountOutOfBin = reserve;
} else if (params.exactInput) {
fees = state.fp.getFeeAmountDistribution(state.fp.getFeeAmountFrom(state.amountSpecifiedRemaining));
amountInToBin = state.amountSpecifiedRemaining - fees.total;
amountOutOfBin = params.zeroForOne
? price.mulShiftRoundDown(amountInToBin, Constants.SCALE_OFFSET)
: amountInToBin.shiftDivRoundDown(Constants.SCALE_OFFSET, price);
// Safety check in case rounding returns a higher value than expected
if (amountOutOfBin > reserve) amountOutOfBin = reserve;
} else {
amountOutOfBin = state.amountSpecifiedRemaining;
amountInToBin = params.zeroForOne
? amountOutOfBin.shiftDivRoundUp(Constants.SCALE_OFFSET, price)
: price.mulShiftRoundUp(amountOutOfBin, Constants.SCALE_OFFSET);
fees = state.fp.getFeeAmountDistribution(state.fp.getFeeAmount(amountInToBin));
}
updateBinReserves(state, params, amountInToBin, amountOutOfBin, fees);
}
/// @notice updates the in memory bin reserves to reflect the completion of a swap
/// @param state the current state of the underlying pool
/// @param params helper params describing the multiswap conditions
/// @param amountInToBin the amount of tokenX/tokenY going into the current bin
/// @param amountOutOfBin the amount of tokenX/tokenY going out of the current bin
/// @param fees the total fees paid in input token resulting from this swap
function updateBinReserves(
SwapState memory state,
SwapParams memory params,
uint256 amountInToBin,
uint256 amountOutOfBin,
FeeHelper.FeesDistribution memory fees
) private view {
FeeHelper.FeesDistribution memory feesX;
FeeHelper.FeesDistribution memory feesY;
(feesX.total, feesY.total, feesX.protocol, feesY.protocol) = ILBPair(params.pool).getGlobalFees();
state.bin.updateFees(
params.zeroForOne ? feesX : feesY,
fees,
params.zeroForOne,
ILBToken(params.pool).totalSupply(state.activeId)
);
if (params.zeroForOne) {
state.bin.reserveX += amountInToBin.safe112();
unchecked {
state.bin.reserveY -= amountOutOfBin.safe112();
}
} else {
state.bin.reserveY += amountInToBin.safe112();
unchecked {
state.bin.reserveX -= amountOutOfBin.safe112();
}
}
}
/// @notice a helper function to absolute value an int256 to uint256
function abs(int256 x) private pure returns (uint256) {
return x > 0 ? uint256(x) : uint256(-x);
}
/// @inheritdoc MultiQuoter
function multiswap(
address p,
bool zeroForOne,
int256[] memory amounts
) internal view override returns (MultiSwapResult memory result) {
result.gasEstimates = new uint256[](amounts.length);
result.amounts0 = new int256[](amounts.length);
result.amounts1 = new int256[](amounts.length);
ILBPair pair = ILBPair(p);
SwapParams memory params = SwapParams({exactInput: amounts[0] > 0, zeroForOne: zeroForOne, pool: p});
SwapState memory state = SwapState({
amountSpecifiedRemaining: abs(amounts[0]),
amountCalculated: 0,
amountsIndex: 0,
bin: ILBPair.Bin(0, 0, 0, 0),
activeId: 0,
fp: FeeHelper.FeeParameters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
pairReserveX: 0,
pairReserveY: 0,
gasAggregate: 0
});
// get the active ID and initialize the fee parameters
(state.pairReserveX, state.pairReserveY, state.activeId) = pair.getReservesAndId();
state.fp = pair.feeParameters();
state.fp.updateVariableFeeParameters(state.activeId);
// initialize the bin based on current active ID
(uint256 reserveX, uint256 reserveY) = pair.getBin(uint24(state.activeId));
state.bin = ILBPair.Bin(uint112(reserveX), uint112(reserveY), 0, 0);
// Performs the actual swap, bin per bin
// It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at
// has liquidity in it.
uint256 amountInToBin;
uint256 amountOutOfBin;
FeeHelper.FeesDistribution memory fees;
uint256 gasBefore;
while (true) {
gasBefore = gasleft();
// exit early if there isn't enough liquidity to satisfy an exact output swap
if (
!params.exactInput &&
(
params.zeroForOne
? state.amountSpecifiedRemaining > state.pairReserveY
: state.amountSpecifiedRemaining > state.pairReserveX
)
) {
return result;
}
if ((!zeroForOne && state.bin.reserveX != 0) || (zeroForOne && state.bin.reserveY != 0)) {
(amountInToBin, amountOutOfBin, fees) = getAmountsAndUpdateBin(state, params);
if (params.exactInput) {
state.amountSpecifiedRemaining -= amountInToBin + fees.total;
state.amountCalculated += amountOutOfBin;
} else {
state.amountSpecifiedRemaining -= amountOutOfBin;
state.amountCalculated += amountInToBin + fees.total;
// update pair reserves for exact output swaps
if (params.zeroForOne) {
state.pairReserveY -= amountOutOfBin;
} else {
state.pairReserveX -= amountOutOfBin;
}
}
}
if (state.amountSpecifiedRemaining != 0) {
state.gasAggregate += gasBefore - gasleft();
try pair.findFirstNonEmptyBinId(uint24(state.activeId), params.zeroForOne) returns (uint24 id) {
state.activeId = id;
} catch {
// we have hit the max/min bin so return what we have so far
return result;
}
(reserveX, reserveY) = pair.getBin(uint24(state.activeId));
state.bin = ILBPair.Bin(uint112(reserveX), uint112(reserveY), 0, 0);
} else {
(result.amounts0[state.amountsIndex], result.amounts1[state.amountsIndex]) = params.zeroForOne ==
params.exactInput
? (amounts[state.amountsIndex], int256(state.amountCalculated))
: (int256(state.amountCalculated), amounts[state.amountsIndex]);
// adjust amount signage to adhere to MultiQuoter assumptions
if (params.exactInput && params.zeroForOne) {
result.amounts1[state.amountsIndex] *= -1;
} else if (params.exactInput && !params.zeroForOne) {
result.amounts0[state.amountsIndex] *= -1;
}
result.gasEstimates[state.amountsIndex] = state.gasAggregate + (gasBefore - gasleft());
if (state.amountsIndex == amounts.length - 1) {
return result;
}
state.amountsIndex += 1;
state.amountSpecifiedRemaining = abs(amounts[state.amountsIndex] - amounts[state.amountsIndex - 1]);
}
}
}
/// @inheritdoc MultiQuoter
function pathHasMultiplePools(bytes memory path) internal pure override returns (bool) {
return path.hasMultiplePools();
}
/// @inheritdoc MultiQuoter
function pathSkipToken(bytes memory path) internal pure override returns (bytes memory) {
return path.skipToken();
}
}

View File

@ -0,0 +1,144 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IMultiQuoter.sol";
import "./TraderJoeV2Common.sol";
contract TraderJoeV2Sampler is TraderJoeV2Common {
uint256 private constant SAMPLING_GAS_LIMIT = 1500e3;
/// @dev Sample sell quotes from Trader Joe V2
/// @param quoter Trader Joe V2 MultiQuoter contract
/// @param factory Trader Joe V2 factory contract
/// @param path Token route. Should be takerToken --> makerToken (at most two hops)
/// @param inputAmounts Taker token sell amounts for each sample
/// @return binSteps The bin steps of the path to use
/// @return gasEstimates Estimated amount of gas used
/// @return outputAmounts Maker amounts bought at each taker token amount
function sampleSellsFromTraderJoeV2(
IMultiQuoter quoter,
address factory,
address[] memory path,
uint256[] memory inputAmounts
) public view returns (uint256[][] memory binSteps, uint256[] memory gasEstimates, uint256[] memory outputAmounts) {
binSteps = new uint256[][](inputAmounts.length);
gasEstimates = new uint256[](inputAmounts.length);
outputAmounts = new uint256[](inputAmounts.length);
address[][] memory poolPaths = getTraderJoeV2PoolPaths(factory, path);
for (uint256 i = 0; i < poolPaths.length; ++i) {
if (!isValidPoolPath(poolPaths[i])) {
continue;
}
bytes memory dexPath = toTraderJoeV2Path(path, poolPaths[i]);
uint256[] memory amountsOut;
uint256[] memory gasEstimatesTemp;
try quoter.quoteExactMultiInput{gas: SAMPLING_GAS_LIMIT}(factory, dexPath, inputAmounts) {} catch (
bytes memory reason
) {
bool success;
(success, amountsOut, gasEstimatesTemp) = decodeMultiSwapRevert(reason);
if (!success) {
continue;
}
for (uint256 j = 0; j < amountsOut.length; ++j) {
if (amountsOut[j] == 0) {
break;
}
if (outputAmounts[j] < amountsOut[j]) {
binSteps[j] = getBinStepsFromPoolPath(poolPaths[i]);
gasEstimates[j] = gasEstimatesTemp[j];
outputAmounts[j] = amountsOut[j];
} else if (outputAmounts[j] == amountsOut[j] && gasEstimates[j] > gasEstimatesTemp[j]) {
binSteps[j] = getBinStepsFromPoolPath(poolPaths[i]);
gasEstimates[j] = gasEstimatesTemp[j];
}
}
}
}
}
/// @dev Sample buy quotes from Trader Joe V2
/// @param quoter Trader Joe V2 MultiQuoter contract
/// @param factory Trader Joe V2 factory contract
/// @param path Token route. Should be takerToken -> makerToken (at most two hops).
/// @param outputAmounts Maker token buy amount for each sample.
/// @return binSteps The bin steps of the path to use
/// @return gasEstimates Estimated amount of gas used
/// @return inputAmounts Taker amounts sold at each maker token amount.
function sampleBuysFromTraderJoeV2(
IMultiQuoter quoter,
address factory,
address[] memory path,
uint256[] memory outputAmounts
) public view returns (uint256[][] memory binSteps, uint256[] memory gasEstimates, uint256[] memory inputAmounts) {
binSteps = new uint256[][](inputAmounts.length);
gasEstimates = new uint256[](inputAmounts.length);
outputAmounts = new uint256[](inputAmounts.length);
address[] memory reversedPath = reverseAddressPath(path);
address[][] memory poolPaths = getTraderJoeV2PoolPaths(factory, reversedPath);
for (uint256 i = 0; i < poolPaths.length; ++i) {
if (!isValidPoolPath(poolPaths[i])) {
continue;
}
bytes memory poolPath = toTraderJoeV2Path(reversedPath, poolPaths[i]);
uint256[] memory amountsIn;
uint256[] memory gasEstimatesTemp;
try quoter.quoteExactMultiOutput{gas: SAMPLING_GAS_LIMIT}(factory, poolPath, outputAmounts) {} catch (
bytes memory reason
) {
bool success;
(success, amountsIn, gasEstimatesTemp) = decodeMultiSwapRevert(reason);
if (!success) {
continue;
}
for (uint256 j = 0; j < amountsIn.length; ++j) {
if (amountsIn[j] == 0) {
break;
}
if (inputAmounts[j] == 0 || inputAmounts[j] > amountsIn[j]) {
binSteps[j] = getBinStepsFromPoolPath(reverseAddressPath(poolPaths[i]));
gasEstimates[j] = gasEstimatesTemp[j];
inputAmounts[j] = amountsIn[j];
} else if (inputAmounts[j] == amountsIn[j] && inputAmounts[j] > gasEstimatesTemp[j]) {
binSteps[j] = getBinStepsFromPoolPath(reverseAddressPath(poolPaths[i]));
gasEstimates[j] = gasEstimatesTemp[j];
}
}
}
}
}
}

View File

@ -0,0 +1,152 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
contract TwoHopSampler {
using LibBytesV06 for bytes;
struct HopInfo {
uint256 sourceIndex;
bytes returnData;
}
function sampleTwoHopSell(
bytes[] memory firstHopCalls,
bytes[] memory secondHopCalls,
uint256 numSamples
) public returns (HopInfo memory firstHop, HopInfo memory secondHop, uint256[] memory buyAmounts) {
buyAmounts = new uint256[](numSamples);
uint256[] memory intermediateAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < firstHopCalls.length; i++) {
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
if (didSucceed) {
uint256[] memory amounts = getAmounts(returnData, numSamples);
// Use the amount from the largest size for comparison.
if (amounts[numSamples - 1] > intermediateAmounts[numSamples - 1]) {
firstHop.sourceIndex = i;
firstHop.returnData = returnData;
for (uint256 j = 0; j < numSamples; j++) {
intermediateAmounts[j] = amounts[j];
}
}
}
}
if (intermediateAmounts[numSamples - 1] == 0) {
return (firstHop, secondHop, buyAmounts);
}
for (uint256 i = 0; i < secondHopCalls.length; i++) {
writeAmounts(secondHopCalls[i], intermediateAmounts);
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[i]);
if (didSucceed) {
uint256[] memory amounts = getAmounts(returnData, numSamples);
// Use the amount from the largest size for comparison.
if (amounts[numSamples - 1] > buyAmounts[numSamples - 1]) {
secondHop.sourceIndex = i;
secondHop.returnData = returnData;
for (uint256 j = 0; j < numSamples; j++) {
buyAmounts[j] = amounts[j];
}
}
}
}
}
function sampleTwoHopBuy(
bytes[] memory firstHopCalls,
bytes[] memory secondHopCalls,
uint256 numSamples
) public returns (HopInfo memory firstHop, HopInfo memory secondHop, uint256[] memory sellAmounts) {
sellAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
sellAmounts[i] = uint256(-1);
}
uint256[] memory intermediateAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
intermediateAmounts[i] = uint256(-1);
}
for (uint256 i = 0; i < secondHopCalls.length; i++) {
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[i]);
if (didSucceed) {
uint256[] memory amounts = getAmounts(returnData, numSamples);
uint256 largestAmount = amounts[numSamples - 1];
// Use the amount from the largest size for comparison.
if (largestAmount > 0 && largestAmount < intermediateAmounts[numSamples - 1]) {
secondHop.sourceIndex = i;
secondHop.returnData = returnData;
for (uint256 j = 0; j < numSamples; j++) {
intermediateAmounts[j] = amounts[j];
}
}
}
}
if (intermediateAmounts[numSamples - 1] == uint256(-1)) {
return (firstHop, secondHop, sellAmounts);
}
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
writeAmounts(firstHopCalls[i], intermediateAmounts);
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
if (didSucceed) {
uint256[] memory amounts = getAmounts(returnData, numSamples);
uint256 largestAmount = amounts[numSamples - 1];
// Use the amount from the largest size for comparison.
if (largestAmount > 0 && largestAmount < sellAmounts[numSamples - 1]) {
firstHop.sourceIndex = i;
firstHop.returnData = returnData;
for (uint256 j = 0; j < numSamples; j++) {
sellAmounts[j] = amounts[j];
}
}
}
}
}
/// @dev Extract amounts from `data` by creating a copy assuming that such uint256[] array exists
/// at the end of `data`.
function getAmounts(bytes memory data, uint256 amountsLength) private pure returns (uint256[] memory) {
uint256 start = data.length - (amountsLength + 2) * 32; // Copy offset and length as well.
uint256 end = data.length;
bytes memory amounts = data.slice(start, end);
amounts.writeUint256(0, 0x20); // Overwrite offset.
return abi.decode(amounts, (uint256[]));
}
/// @dev Writes amounts arary to the end of data assuming that there is space reserved.
function writeAmounts(bytes memory data, uint256[] memory amounts) private pure {
for (uint256 i = 0; i < amounts.length; i++) {
uint256 index = data.length - 32 * (amounts.length - i - 1);
uint256 amount = amounts[i];
assembly {
mstore(add(data, index), amount)
}
}
}
}

View File

@ -0,0 +1,191 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IUniswapExchangeQuotes.sol";
import "./SamplerUtils.sol";
interface IUniswapExchangeFactory {
/// @dev Get the exchange for a token.
/// @param tokenAddress The address of the token contract.
function getExchange(address tokenAddress) external view returns (address);
}
contract UniswapSampler is SamplerUtils {
/// @dev Gas limit for Uniswap calls.
uint256 private constant UNISWAP_CALL_GAS = 150e3; // 150k
/// @dev Sample sell quotes from Uniswap.
/// @param router Address of the Uniswap Router
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromUniswap(
address router,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0)
? IUniswapExchangeQuotes(0)
: _getUniswapExchange(router, takerToken);
IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0)
? IUniswapExchangeQuotes(0)
: _getUniswapExchange(router, makerToken);
for (uint256 i = 0; i < numSamples; i++) {
bool didSucceed = true;
if (makerToken == address(0)) {
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i]
);
} else if (takerToken == address(0)) {
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector,
takerTokenAmounts[i]
);
} else {
uint256 ethBought;
(ethBought, didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i]
);
if (ethBought != 0) {
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector,
ethBought
);
} else {
makerTokenAmounts[i] = 0;
}
}
// Break early if amounts are 0
if (!didSucceed || makerTokenAmounts[i] == 0) {
break;
}
}
}
/// @dev Sample buy quotes from Uniswap.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromUniswap(
address router,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0)
? IUniswapExchangeQuotes(0)
: _getUniswapExchange(router, takerToken);
IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0)
? IUniswapExchangeQuotes(0)
: _getUniswapExchange(router, makerToken);
for (uint256 i = 0; i < numSamples; i++) {
bool didSucceed = true;
if (makerToken == address(0)) {
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector,
makerTokenAmounts[i]
);
} else if (takerToken == address(0)) {
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i]
);
} else {
uint256 ethSold;
(ethSold, didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i]
);
if (ethSold != 0) {
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector,
ethSold
);
} else {
takerTokenAmounts[i] = 0;
}
}
// Break early if amounts are 0
if (!didSucceed || takerTokenAmounts[i] == 0) {
break;
}
}
}
/// @dev Gracefully calls a Uniswap pricing function.
/// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange.
/// @param functionSelector Selector of the target function.
/// @param inputAmount Quantity parameter particular to the pricing function.
/// @return outputAmount The returned amount from the function call. Will be
/// zero if the call fails or if `uniswapExchangeAddress` is zero.
function _callUniswapExchangePriceFunction(
address uniswapExchangeAddress,
bytes4 functionSelector,
uint256 inputAmount
) private view returns (uint256 outputAmount, bool didSucceed) {
if (uniswapExchangeAddress == address(0)) {
return (outputAmount, didSucceed);
}
bytes memory resultData;
(didSucceed, resultData) = uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)(
abi.encodeWithSelector(functionSelector, inputAmount)
);
if (didSucceed) {
outputAmount = abi.decode(resultData, (uint256));
}
}
/// @dev Retrive an existing Uniswap exchange contract.
/// Throws if the exchange does not exist.
/// @param router Address of the Uniswap router.
/// @param tokenAddress Address of the token contract.
/// @return exchange `IUniswapExchangeQuotes` for the token.
function _getUniswapExchange(
address router,
address tokenAddress
) private view returns (IUniswapExchangeQuotes exchange) {
exchange = IUniswapExchangeQuotes(address(IUniswapExchangeFactory(router).getExchange(tokenAddress)));
}
}

View File

@ -0,0 +1,86 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./interfaces/IUniswapV2Router01.sol";
contract UniswapV2Sampler {
/// @dev Gas limit for UniswapV2 calls.
uint256 private constant UNISWAPV2_CALL_GAS = 150e3; // 150k
/// @dev Sample sell quotes from UniswapV2.
/// @param router Router to look up tokens and amounts
/// @param path Token route. Should be takerToken -> makerToken
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromUniswapV2(
address router,
address[] memory path,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
try IUniswapV2Router01(router).getAmountsOut{gas: UNISWAPV2_CALL_GAS}(takerTokenAmounts[i], path) returns (
uint256[] memory amounts
) {
makerTokenAmounts[i] = amounts[path.length - 1];
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
/// @dev Sample buy quotes from UniswapV2.
/// @param router Router to look up tokens and amounts
/// @param path Token route. Should be takerToken -> makerToken.
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromUniswapV2(
address router,
address[] memory path,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
try IUniswapV2Router01(router).getAmountsIn{gas: UNISWAPV2_CALL_GAS}(makerTokenAmounts[i], path) returns (
uint256[] memory amounts
) {
takerTokenAmounts[i] = amounts[0];
// Break early if there are 0 amounts
if (takerTokenAmounts[i] == 0) {
break;
}
} catch (bytes memory) {
// Swallow failures, leaving all results as zero.
break;
}
}
}
}

View File

@ -0,0 +1,175 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity >=0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "./interfaces/IUniswapV3.sol";
import "./TickBasedAMMCommon.sol";
contract UniswapV3Common is TickBasedAMMCommon {
function toUniswapPath(
address[] memory tokenPath,
address[] memory poolPath
) internal view returns (bytes memory uniswapPath) {
require(
tokenPath.length >= 2 && tokenPath.length == poolPath.length + 1,
"UniswapV3Common/invalid path lengths"
);
// Uniswap paths are tightly packed as:
// [token0, token0token1PairFee, token1, token1Token2PairFee, token2, ...]
uniswapPath = new bytes(tokenPath.length * 20 + poolPath.length * 3);
uint256 o;
assembly {
o := add(uniswapPath, 32)
}
for (uint256 i = 0; i < tokenPath.length; ++i) {
if (i > 0) {
uint24 poolFee = IUniswapV3Pool(poolPath[i - 1]).fee();
assembly {
mstore(o, shl(232, poolFee))
o := add(o, 3)
}
}
address token = tokenPath[i];
assembly {
mstore(o, shl(96, token))
o := add(o, 20)
}
}
}
/// @dev Returns `poolPaths` to sample against. The caller is responsible for not using path involinvg zero address(es).
function getPoolPaths(address factory, address[] memory path) internal view returns (address[][] memory poolPaths) {
if (path.length == 2) {
return getPoolPathSingleHop(factory, path);
}
if (path.length == 3) {
return getPoolPathTwoHop(factory, path);
}
revert("UniswapV3Sampler/unsupported token path length");
}
function getPoolPathSingleHop(
address factory,
address[] memory path
) private view returns (address[][] memory poolPaths) {
poolPaths = new address[][](2);
address[2] memory topPools = getTopTwoPools(
GetTopTwoPoolsParams({factory: factory, inputToken: path[0], outputToken: path[1]})
);
uint256 pathCount = 0;
for (uint256 i = 0; i < 2; i++) {
address topPool = topPools[i];
poolPaths[pathCount] = new address[](1);
poolPaths[pathCount][0] = topPool;
pathCount++;
}
}
function getPoolPathTwoHop(
address factory,
address[] memory path
) private view returns (address[][] memory poolPaths) {
poolPaths = new address[][](4);
address[2] memory firstHopTopPools = getTopTwoPools(
GetTopTwoPoolsParams({factory: factory, inputToken: path[0], outputToken: path[1]})
);
address[2] memory secondHopTopPools = getTopTwoPools(
GetTopTwoPoolsParams({factory: factory, inputToken: path[1], outputToken: path[2]})
);
uint256 pathCount = 0;
for (uint256 i = 0; i < 2; i++) {
for (uint256 j = 0; j < 2; j++) {
poolPaths[pathCount] = new address[](2);
address[] memory currentPath = poolPaths[pathCount];
currentPath[0] = firstHopTopPools[i];
currentPath[1] = secondHopTopPools[j];
pathCount++;
}
}
}
struct GetTopTwoPoolsParams {
address factory;
address inputToken;
address outputToken;
}
/// @dev Returns top 0-2 pools and corresponding output amounts based on swaping `inputAmount`.
/// Addresses in `topPools` can be zero addresses when there are pool isn't available.
function getTopTwoPools(GetTopTwoPoolsParams memory params) private view returns (address[2] memory topPools) {
address[] memory path = new address[](2);
path[0] = params.inputToken;
path[1] = params.outputToken;
uint24[4] memory validPoolFees = [uint24(0.0001e6), uint24(0.0005e6), uint24(0.003e6), uint24(0.01e6)];
uint128[2] memory topLiquidityAmounts;
for (uint256 i = 0; i < validPoolFees.length; ++i) {
address pool = IUniswapV3Factory(params.factory).getPool(
params.inputToken,
params.outputToken,
validPoolFees[i]
);
if (!isValidPool(pool)) {
continue;
}
uint128 currLiquidity = IUniswapV3Pool(pool).liquidity();
if (currLiquidity > topLiquidityAmounts[0]) {
topLiquidityAmounts[1] = topLiquidityAmounts[0];
topPools[1] = topPools[0];
topLiquidityAmounts[0] = currLiquidity;
topPools[0] = pool;
} else if (currLiquidity > topLiquidityAmounts[1]) {
topLiquidityAmounts[1] = currLiquidity;
topPools[1] = pool;
}
}
}
function isValidPool(address pool) internal view returns (bool isValid) {
// Check if it has been deployed.
uint256 codeSize;
assembly {
codeSize := extcodesize(pool)
}
if (codeSize == 0) {
return false;
}
// Must have a balance of both tokens.
IERC20TokenV06 token0 = IERC20TokenV06(IUniswapV3Pool(pool).token0());
if (token0.balanceOf(pool) == 0) {
return false;
}
IERC20TokenV06 token1 = IERC20TokenV06(IUniswapV3Pool(pool).token1());
if (token1.balanceOf(pool) == 0) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,286 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.7;
pragma experimental ABIEncoderV2;
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import "@uniswap/v3-core/contracts/libraries/SwapMath.sol";
import "@uniswap/v3-periphery/contracts/libraries/Path.sol";
import "@uniswap/v3-core/contracts/libraries/LiquidityMath.sol";
import "@uniswap/v3-core/contracts/libraries/BitMath.sol";
import "@uniswap/v3-core/contracts/libraries/SafeCast.sol";
import "@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol";
import "./interfaces/IUniswapV3.sol";
import "./MultiQuoter.sol";
contract UniswapV3MultiQuoter is MultiQuoter {
using Path for bytes;
using SafeCast for uint256;
using LowGasSafeMath for int256;
// the top level state of the multiswap, the results are transient.
struct SwapState {
// the amount remaining to be swapped in/out of the input/output asset
int256 amountSpecifiedRemaining;
// the amount already swapped out/in of the output/input asset
int256 amountCalculated;
// current sqrt(price)
uint160 sqrtPriceX96;
// the tick associated with the current price
int24 tick;
// the current liquidity in range
uint128 liquidity;
// the current quote amount we are querying liquidity for
uint256 amountsIndex;
// the current aggregate gas estimate for multi swap
uint256 gasAggregate;
}
// the intermediate calculations for each tick and quote amount
struct StepComputations {
// the price at the beginning of the step
uint160 sqrtPriceStartX96;
// the next tick to swap to from the current tick in the swap direction
int24 tickNext;
// whether tickNext is initialized or not
bool initialized;
// sqrt(price) for the next tick (1/0)
uint160 sqrtPriceNextX96;
// how much is being swapped in in this step
uint256 amountIn;
// how much is being swapped out
uint256 amountOut;
// how much fee is being paid in
uint256 feeAmount;
// how much gas was left before the current step
uint256 gasBefore;
}
/// @inheritdoc MultiQuoter
function getFirstSwapDetails(
address factory,
bytes memory path,
bool isExactInput
) internal view override returns (address pool, bool zeroForOne) {
address tokenIn;
address tokenOut;
uint24 fee;
if (isExactInput) {
(tokenIn, tokenOut, fee) = path.decodeFirstPool();
} else {
(tokenOut, tokenIn, fee) = path.decodeFirstPool();
}
zeroForOne = tokenIn < tokenOut;
pool = IUniswapV3Factory(factory).getPool(tokenIn, tokenOut, fee);
}
/// @inheritdoc MultiQuoter
function multiswap(
address p,
bool zeroForOne,
int256[] memory amounts
) internal view override returns (MultiSwapResult memory result) {
result.gasEstimates = new uint256[](amounts.length);
result.amounts0 = new int256[](amounts.length);
result.amounts1 = new int256[](amounts.length);
uint160 sqrtPriceLimitX96 = zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
IUniswapV3Pool pool = IUniswapV3Pool(p);
(uint160 sqrtPriceX96Start, int24 tickStart, , , , , ) = pool.slot0();
int24 tickSpacing = pool.tickSpacing();
uint24 fee = pool.fee();
bool exactInput = amounts[0] > 0;
SwapState memory state = SwapState({
amountSpecifiedRemaining: amounts[0],
amountCalculated: 0,
sqrtPriceX96: sqrtPriceX96Start,
tick: tickStart,
liquidity: pool.liquidity(),
amountsIndex: 0,
gasAggregate: 0
});
// continue swapping as long as we haven't used the entire input/output and haven't reached the price limit
while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
StepComputations memory step;
step.gasBefore = gasleft();
step.sqrtPriceStartX96 = state.sqrtPriceX96;
(step.tickNext, step.initialized) = nextInitializedTickWithinOneWord(
pool,
state.tick,
tickSpacing,
zeroForOne
);
// ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds
if (step.tickNext < TickMath.MIN_TICK) {
step.tickNext = TickMath.MIN_TICK;
} else if (step.tickNext > TickMath.MAX_TICK) {
step.tickNext = TickMath.MAX_TICK;
}
// get the price for the next tick
step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext);
// compute values to swap to the target tick, price limit, or point where input/output amount is exhausted
(state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep(
state.sqrtPriceX96,
(zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96)
? sqrtPriceLimitX96
: step.sqrtPriceNextX96,
state.liquidity,
state.amountSpecifiedRemaining,
fee
);
if (exactInput) {
state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256();
state.amountCalculated = state.amountCalculated.sub(step.amountOut.toInt256());
} else {
state.amountSpecifiedRemaining += step.amountOut.toInt256();
state.amountCalculated = state.amountCalculated.add((step.amountIn + step.feeAmount).toInt256());
}
// shift tick if we reached the next price
if (state.sqrtPriceX96 == step.sqrtPriceNextX96) {
// if the tick is initialized, run the tick transition
if (step.initialized) {
(, int128 liquidityNet, , , , , , ) = pool.ticks(step.tickNext);
// if we're moving leftward, we interpret liquidityNet as the opposite sign
// safe because liquidityNet cannot be type(int128).min
if (zeroForOne) liquidityNet = -liquidityNet;
state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet);
}
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
state.gasAggregate += step.gasBefore - gasleft();
} else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96);
}
if (state.amountSpecifiedRemaining == 0) {
(result.amounts0[state.amountsIndex], result.amounts1[state.amountsIndex]) = zeroForOne == exactInput
? (amounts[state.amountsIndex], state.amountCalculated)
: (state.amountCalculated, amounts[state.amountsIndex]);
if (state.sqrtPriceX96 != step.sqrtPriceNextX96) {
result.gasEstimates[state.amountsIndex] = scaleMultiswapGasEstimate(
state.gasAggregate + (step.gasBefore - gasleft())
);
} else {
// we are moving to the next tick
result.gasEstimates[state.amountsIndex] = scaleMultiswapGasEstimate(state.gasAggregate);
}
if (state.amountsIndex == amounts.length - 1) {
return (result);
}
state.amountsIndex += 1;
state.amountSpecifiedRemaining = amounts[state.amountsIndex].sub(amounts[state.amountsIndex - 1]);
}
}
for (uint256 i = state.amountsIndex; i < amounts.length; ++i) {
(result.amounts0[i], result.amounts1[i]) = zeroForOne == exactInput
? (amounts[i] - state.amountSpecifiedRemaining, state.amountCalculated)
: (state.amountCalculated, amounts[i] - state.amountSpecifiedRemaining);
result.gasEstimates[i] = scaleMultiswapGasEstimate(state.gasAggregate);
}
}
/// @notice Returns the multiswap gas estimate scaled to UniswapV3Pool:swap estimates
/// @dev These parameters have been determined by running a linear regression between UniswapV3QuoterV2
/// gas estimates and the unscaled gas estimates from multiswap
/// @param multiSwapEstimate the gas estimate from multiswap
/// @return swapEstimate the gas estimate equivalent for UniswapV3Pool:swap
function scaleMultiswapGasEstimate(uint256 multiSwapEstimate) private pure returns (uint256 swapEstimate) {
return (166 * multiSwapEstimate) / 100 + 50000;
}
/// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either
/// to the left (less than or equal to) or right (greater than) of the given tick
/// @param pool The UniswapV3 pool to get next tick for
/// @param tick The starting tick
/// @param tickSpacing The spacing between usable ticks
/// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick)
/// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick
/// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks
function nextInitializedTickWithinOneWord(
IUniswapV3Pool pool,
int24 tick,
int24 tickSpacing,
bool lte
) internal view returns (int24 next, bool initialized) {
int24 compressed = tick / tickSpacing;
if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity
if (lte) {
(int16 wordPos, uint8 bitPos) = position(compressed);
// all the 1s at or to the right of the current bitPos
uint256 mask = (1 << bitPos) - 1 + (1 << bitPos);
uint256 masked = pool.tickBitmap(wordPos) & mask;
// if there are no initialized ticks to the right of or at the current tick, return rightmost in the word
initialized = masked != 0;
// overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick
next = initialized
? (compressed - int24(bitPos - BitMath.mostSignificantBit(masked))) * tickSpacing
: (compressed - int24(bitPos)) * tickSpacing;
} else {
// start from the word of the next tick, since the current tick state doesn't matter
(int16 wordPos, uint8 bitPos) = position(compressed + 1);
// all the 1s at or to the left of the bitPos
uint256 mask = ~((1 << bitPos) - 1);
uint256 masked = pool.tickBitmap(wordPos) & mask;
// if there are no initialized ticks to the left of the current tick, return leftmost in the word
initialized = masked != 0;
// overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick
next = initialized
? (compressed + 1 + int24(BitMath.leastSignificantBit(masked) - bitPos)) * tickSpacing
: (compressed + 1 + int24(type(uint8).max - bitPos)) * tickSpacing;
}
}
/// @notice Computes the position in the mapping where the initialized bit for a tick lives
/// @param tick The tick for which to compute the position
/// @return wordPos The key in the mapping containing the word in which the bit is stored
/// @return bitPos The bit position in the word where the flag is stored
function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) {
wordPos = int16(tick >> 8);
bitPos = uint8(tick % 256);
}
/// @inheritdoc MultiQuoter
function pathHasMultiplePools(bytes memory path) internal pure override returns (bool) {
return path.hasMultiplePools();
}
/// @inheritdoc MultiQuoter
function pathSkipToken(bytes memory path) internal pure override returns (bytes memory) {
return path.skipToken();
}
}

View File

@ -0,0 +1,151 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "./UniswapV3Common.sol";
import "./interfaces/IMultiQuoter.sol";
contract UniswapV3Sampler is UniswapV3Common {
IMultiQuoter private constant multiQuoter = IMultiQuoter(0x5555555555555555555555555555555555555556);
/// @dev Gas limit for UniswapV3 calls
uint256 private constant SAMPLING_GAS_LIMIT = 1500e3;
/// @dev Sample sell quotes from UniswapV3.
/// @param factory UniswapV3 Factory contract.
/// @param path Token route. Should be takerToken -> makerToken (at most two hops).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return uniswapPaths The encoded uniswap path for each sample.
/// @return uniswapGasUsed Estimated amount of gas used
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
function sampleSellsFromUniswapV3(
address factory,
address[] memory path,
uint256[] memory takerTokenAmounts
)
public
returns (bytes[] memory uniswapPaths, uint256[] memory uniswapGasUsed, uint256[] memory makerTokenAmounts)
{
address[][] memory poolPaths = getPoolPaths(factory, path);
makerTokenAmounts = new uint256[](takerTokenAmounts.length);
uniswapPaths = new bytes[](takerTokenAmounts.length);
uniswapGasUsed = new uint256[](takerTokenAmounts.length);
for (uint256 i = 0; i < poolPaths.length; ++i) {
if (!isValidPoolPath(poolPaths[i])) {
continue;
}
bytes memory uniswapPath = toUniswapPath(path, poolPaths[i]);
uint256[] memory amountsOut;
uint256[] memory gasEstimates;
try
multiQuoter.quoteExactMultiInput{gas: SAMPLING_GAS_LIMIT}(factory, uniswapPath, takerTokenAmounts)
{} catch (bytes memory reason) {
bool success;
(success, amountsOut, gasEstimates) = decodeMultiSwapRevert(reason);
if (!success) {
continue;
}
for (uint256 j = 0; j < amountsOut.length; ++j) {
if (amountsOut[j] == 0) {
break;
}
if (makerTokenAmounts[j] < amountsOut[j]) {
makerTokenAmounts[j] = amountsOut[j];
uniswapPaths[j] = uniswapPath;
uniswapGasUsed[j] = gasEstimates[j];
} else if (makerTokenAmounts[j] == amountsOut[j] && uniswapGasUsed[j] > gasEstimates[j]) {
uniswapPaths[j] = uniswapPath;
uniswapGasUsed[j] = gasEstimates[j];
}
}
}
}
}
/// @dev Sample buy quotes from UniswapV3.
/// @param factory UniswapV3 Factory contract.
/// @param path Token route. Should be takerToken -> makerToken (at most two hops).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return uniswapPaths The encoded uniswap path for each sample.
/// @return uniswapGasUsed Estimated amount of gas used
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
function sampleBuysFromUniswapV3(
address factory,
address[] memory path,
uint256[] memory makerTokenAmounts
)
public
returns (bytes[] memory uniswapPaths, uint256[] memory uniswapGasUsed, uint256[] memory takerTokenAmounts)
{
address[] memory reversedPath = reverseAddressPath(path);
address[][] memory poolPaths = getPoolPaths(factory, reversedPath);
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
uniswapPaths = new bytes[](makerTokenAmounts.length);
uniswapGasUsed = new uint256[](makerTokenAmounts.length);
for (uint256 i = 0; i < poolPaths.length; ++i) {
if (!isValidPoolPath(poolPaths[i])) {
continue;
}
bytes memory uniswapPath = toUniswapPath(reversedPath, poolPaths[i]);
uint256[] memory amountsIn;
uint256[] memory gasEstimates;
try
multiQuoter.quoteExactMultiOutput{gas: SAMPLING_GAS_LIMIT}(factory, uniswapPath, makerTokenAmounts)
{} catch (bytes memory reason) {
bool success;
(success, amountsIn, gasEstimates) = decodeMultiSwapRevert(reason);
if (!success) {
continue;
}
for (uint256 j = 0; j < amountsIn.length; ++j) {
if (amountsIn[j] == 0) {
break;
}
if (takerTokenAmounts[j] == 0 || takerTokenAmounts[j] > amountsIn[j]) {
takerTokenAmounts[j] = amountsIn[j];
uniswapPaths[j] = toUniswapPath(path, reverseAddressPath(poolPaths[i]));
uniswapGasUsed[j] = gasEstimates[j];
} else if (takerTokenAmounts[j] == amountsIn[j] && uniswapGasUsed[j] > gasEstimates[j]) {
uniswapPaths[j] = toUniswapPath(path, reverseAddressPath(poolPaths[i]));
uniswapGasUsed[j] = gasEstimates[j];
}
}
}
}
}
}

View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
contract UtilitySampler {
using LibERC20TokenV06 for IERC20TokenV06;
IERC20TokenV06 private immutable UTILITY_ETH_ADDRESS = IERC20TokenV06(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
function getTokenDecimals(IERC20TokenV06[] memory tokens) public view returns (uint256[] memory decimals) {
decimals = new uint256[](tokens.length);
for (uint256 i = 0; i != tokens.length; i++) {
decimals[i] = tokens[i] == UTILITY_ETH_ADDRESS ? 18 : tokens[i].compatDecimals();
}
}
function getBalanceOf(
IERC20TokenV06[] memory tokens,
address account
) public view returns (uint256[] memory balances) {
balances = new uint256[](tokens.length);
for (uint256 i = 0; i != tokens.length; i++) {
balances[i] = tokens[i] == UTILITY_ETH_ADDRESS ? account.balance : tokens[i].compatBalanceOf(account);
}
}
function getAllowanceOf(
IERC20TokenV06[] memory tokens,
address account,
address spender
) public view returns (uint256[] memory allowances) {
allowances = new uint256[](tokens.length);
for (uint256 i = 0; i != tokens.length; i++) {
allowances[i] = tokens[i] == UTILITY_ETH_ADDRESS ? 0 : tokens[i].compatAllowance(account, spender);
}
}
function isContract(address account) public view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
function getGasLeft() public returns (uint256) {
return gasleft();
}
function getBlockNumber() public view returns (uint256) {
return block.number;
}
}

View File

@ -0,0 +1,134 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
struct VeloRoute {
address from;
address to;
bool stable;
}
interface IVelodromeRouter {
function getAmountOut(
uint256 amountIn,
address tokenIn,
address tokenOut
) external view returns (uint256 amount, bool stable);
function getAmountsOut(
uint256 amountIn,
VeloRoute[] calldata routes
) external view returns (uint256[] memory amounts);
}
contract VelodromeSampler is SamplerUtils, ApproximateBuys {
/// @dev Sample sell quotes from Velodrome
/// @param router Address of Velodrome router.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order).
/// @return stable Whether the pool is a stable pool (vs volatile).
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
function sampleSellsFromVelodrome(
IVelodromeRouter router,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (bool stable, uint256[] memory makerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
// Sampling should not mix stable and volatile pools.
// Find the most liquid pool based on max(takerTokenAmounts) and stick with it.
stable = _isMostLiquidPoolStablePool(router, takerToken, makerToken, takerTokenAmounts);
VeloRoute[] memory routes = new VeloRoute[](1);
routes[0] = VeloRoute({from: takerToken, to: makerToken, stable: stable});
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = router.getAmountsOut(takerTokenAmounts[i], routes)[1];
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
}
}
/// @dev Sample buy quotes from Velodrome.
/// @param router Address of Velodrome router.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return stable Whether the pool is a stable pool (vs volatile).
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
function sampleBuysFromVelodrome(
IVelodromeRouter router,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (bool stable, uint256[] memory takerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
// Sampling should not mix stable and volatile pools.
// Find the most liquid pool based on the reverse swap (maker -> taker) and stick with it.
stable = _isMostLiquidPoolStablePool(router, makerToken, takerToken, makerTokenAmounts);
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
takerTokenData: abi.encode(router, VeloRoute({from: takerToken, to: makerToken, stable: stable})),
makerTokenData: abi.encode(router, VeloRoute({from: makerToken, to: takerToken, stable: stable})),
getSellQuoteCallback: _sampleSellForApproximateBuyFromVelodrome
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromVelodrome(
bytes memory takerTokenData,
bytes memory /* makerTokenData */,
uint256 sellAmount
) internal view returns (uint256) {
(IVelodromeRouter router, VeloRoute memory route) = abi.decode(takerTokenData, (IVelodromeRouter, VeloRoute));
VeloRoute[] memory routes = new VeloRoute[](1);
routes[0] = route;
return router.getAmountsOut(sellAmount, routes)[1];
}
/// @dev Returns whether the most liquid pool is a stable pool.
/// @param router Address of Velodrome router.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token buy amount for each sample (sorted in ascending order)
/// @return stable Whether the pool is a stable pool (vs volatile).
function _isMostLiquidPoolStablePool(
IVelodromeRouter router,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) internal view returns (bool stable) {
uint256 numSamples = takerTokenAmounts.length;
(, stable) = router.getAmountOut(takerTokenAmounts[numSamples - 1], takerToken, makerToken);
}
}

View File

@ -0,0 +1,87 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./SamplerUtils.sol";
import "./ApproximateBuys.sol";
interface IWooPP {
/// @dev query the amount to swap fromToken -> toToken
/// @param fromToken the from token
/// @param toToken the to token
/// @param fromAmount the amount of fromToken to swap
/// @return toAmount the predicted amount to receive
function querySwap(address fromToken, address toToken, uint256 fromAmount) external view returns (uint256 toAmount);
}
contract WooPPSampler is SamplerUtils, ApproximateBuys {
/// @dev Sample sell quotes from WooFI.
/// @param router Address of the router we are sampling from
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order).
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromWooPP(
IWooPP router,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (uint256[] memory makerTokenAmounts) {
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = router.querySwap(takerToken, makerToken, takerTokenAmounts[i]);
if (makerTokenAmounts[i] == 0) {
break;
}
}
}
/// @dev Sample buy quotes from WooFI.
/// @param router Address of the router we are sampling from
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample (sorted in ascending order).
/// @return takerTokenAmounts Taker amounts bought at each taker token
/// amount.
function sampleBuysFromWooPP(
IWooPP router,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (uint256[] memory takerTokenAmounts) {
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
takerTokenData: abi.encode(router, takerToken, makerToken),
makerTokenData: abi.encode(router, makerToken, takerToken),
getSellQuoteCallback: _sampleSellForApproximateBuyFromWoofi
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromWoofi(
bytes memory takerTokenData,
bytes memory makerTokenData,
uint256 sellAmount
) internal view returns (uint256) {
(IWooPP _router, address _takerToken, address _makerToken) = abi.decode(
takerTokenData,
(IWooPP, address, address)
);
(bool success, bytes memory resultData) = address(this).staticcall(
abi.encodeWithSelector(
this.sampleSellsFromWooPP.selector,
_router,
_takerToken,
_makerToken,
_toSingleValueArray(sellAmount)
)
);
if (!success) {
return 0;
}
return abi.decode(resultData, (uint256[]))[0];
}
}

View File

@ -0,0 +1,92 @@
pragma solidity >=0.6;
interface IAlgebraFactory {
/**
* @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
* @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
* @param tokenA The contract address of either token0 or token1
* @param tokenB The contract address of the other token
* @return pool The pool address
*/
function poolByPair(address tokenA, address tokenB) external view returns (address pool);
}
interface IAlgebraPool {
/**
* @notice The globalState structure in the pool stores many values but requires only one slot
* and is exposed as a single method to save gas when accessed externally.
* @return price The current price of the pool as a sqrt(token1/token0) Q64.96 value;
* Returns tick The current tick of the pool, i.e. according to the last tick transition that was run;
* Returns This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(price) if the price is on a tick
* boundary;
* Returns fee The last pool fee value in hundredths of a bip, i.e. 1e-6;
* Returns timepointIndex The index of the last written timepoint;
* Returns communityFeeToken0 The community fee percentage of the swap fee in thousandths (1e-3) for token0;
* Returns communityFeeToken1 The community fee percentage of the swap fee in thousandths (1e-3) for token1;
* Returns unlocked Whether the pool is currently locked to reentrancy;
*/
function globalState()
external
view
returns (
uint160 price,
int24 tick,
uint16 fee,
uint16 timepointIndex,
uint8 communityFeeToken0,
uint8 communityFeeToken1,
bool unlocked
);
/**
* @notice The currently in range liquidity available to the pool
* @dev This value has no relationship to the total liquidity across all ticks.
* Returned value cannot exceed type(uint128).max
*/
function liquidity() external view returns (uint128);
/**
* @notice The pool tick spacing
* @dev Ticks can only be used at multiples of this value
* e.g.: a tickSpacing of 60 means ticks can be initialized every 60th tick, i.e., ..., -120, -60, 0, 60, 120, ...
* This value is an int24 to avoid casting even though it is always positive.
* @return The tick spacing
*/
function tickSpacing() external view returns (int24);
/** @notice Returns 256 packed tick initialized boolean values. See TickTable for more information */
function tickTable(int16 wordPosition) external view returns (uint256);
/**
* @notice Look up information about a specific tick in the pool
* @dev This is a public structure, so the `return` natspec tags are omitted.
* @param tick The tick to look up
* @return liquidityTotal the total amount of position liquidity that uses the pool either as tick lower or
* tick upper;
* Returns liquidityDelta how much liquidity changes when the pool price crosses the tick;
* Returns outerFeeGrowth0Token the fee growth on the other side of the tick from the current tick in token0;
* Returns outerFeeGrowth1Token the fee growth on the other side of the tick from the current tick in token1;
* Returns outerTickCumulative the cumulative tick value on the other side of the tick from the current tick;
* Returns outerSecondsPerLiquidity the seconds spent per liquidity on the other side of the tick from the current tick;
* Returns outerSecondsSpent the seconds spent on the other side of the tick from the current tick;
* Returns initialized Set to true if the tick is initialized, i.e. liquidityTotal is greater than 0
* otherwise equal to false. Outside values can only be used if the tick is initialized.
* In addition, these values are only relative and must be used only in comparison to previous snapshots for
* a specific position.
*/
function ticks(
int24 tick
)
external
view
returns (
uint128 liquidityTotal,
int128 liquidityDelta,
uint256 outerFeeGrowth0Token,
uint256 outerFeeGrowth1Token,
int56 outerTickCumulative,
uint160 outerSecondsPerLiquidity,
uint32 outerSecondsSpent,
bool initialized
);
}

View File

@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
interface IBalancer {
function isBound(address t) external view returns (bool);
function getDenormalizedWeight(address token) external view returns (uint256);
function getBalance(address token) external view returns (uint256);
function getSwapFee() external view returns (uint256);
function calcOutGivenIn(
uint256 tokenBalanceIn,
uint256 tokenWeightIn,
uint256 tokenBalanceOut,
uint256 tokenWeightOut,
uint256 tokenAmountIn,
uint256 swapFee
) external pure returns (uint256 tokenAmountOut);
function calcInGivenOut(
uint256 tokenBalanceIn,
uint256 tokenWeightIn,
uint256 tokenBalanceOut,
uint256 tokenWeightOut,
uint256 tokenAmountOut,
uint256 swapFee
) external pure returns (uint256 tokenAmountIn);
}

View File

@ -0,0 +1,57 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
/// @dev Minimal Balancer V2 Vault interface
/// for documentation refer to https://github.com/balancer-labs/balancer-core-v2/blob/master/contracts/vault/interfaces/IVault.sol
interface IBalancerV2Vault {
enum SwapKind {
GIVEN_IN,
GIVEN_OUT
}
struct BatchSwapStep {
bytes32 poolId;
uint256 assetInIndex;
uint256 assetOutIndex;
uint256 amount;
bytes userData;
}
struct FundManagement {
address sender;
bool fromInternalBalance;
address payable recipient;
bool toInternalBalance;
}
struct BalancerV2PoolInfo {
bytes32 poolId;
address vault;
}
function queryBatchSwap(
SwapKind kind,
BatchSwapStep[] calldata swaps,
address[] calldata assets,
FundManagement calldata funds
) external returns (int256[] memory assetDeltas);
}

View File

@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
interface IBancor {}
interface IBancorNetwork {
function conversionPath(address _sourceToken, address _targetToken) external view returns (address[] memory);
function rateByPath(address[] memory _path, uint256 _amount) external view returns (uint256);
}
interface IBancorRegistry {
function getAddress(bytes32 _contractName) external view returns (address);
function BANCOR_NETWORK() external view returns (bytes32);
}

View File

@ -1,30 +1,41 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 ZeroEx Intl.
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "../src/features/libs/LibNativeOrder.sol";
interface IBancorV3 {
/**
* @dev returns the output amount when trading by providing the source amount
*/
function tradeOutputBySourceAmount(
address sourceToken,
address targetToken,
uint256 sourceAmount
) external view returns (uint256);
contract TestLibNativeOrder {
function getLimitOrderStructHash(
LibNativeOrder.LimitOrder calldata order
) external pure returns (bytes32 structHash) {
return LibNativeOrder.getLimitOrderStructHash(order);
}
function getRfqOrderStructHash(LibNativeOrder.RfqOrder calldata order) external pure returns (bytes32 structHash) {
return LibNativeOrder.getRfqOrderStructHash(order);
}
/**
* @dev returns the input amount when trading by providing the target amount
*/
function tradeInputByTargetAmount(
address sourceToken,
address targetToken,
uint256 targetAmount
) external view returns (uint256);
}

View File

@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
// solhint-disable func-name-mixedcase
interface ICurve {
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
/// This function exists on later versions of Curve (USDC/DAI/USDT)
/// @param i The token index being sold.
/// @param j The token index being bought.
/// @param sellAmount The amount of token being bought.
/// @param minBuyAmount The minimum buy amount of the token being bought.
function exchange_underlying(int128 i, int128 j, uint256 sellAmount, uint256 minBuyAmount) external;
/// @dev Get the amount of `toToken` by selling `sellAmount` of `fromToken`
/// @param i The token index being sold.
/// @param j The token index being bought.
/// @param sellAmount The amount of token being bought.
function get_dy_underlying(int128 i, int128 j, uint256 sellAmount) external returns (uint256 dy);
/// @dev Get the amount of `fromToken` by buying `buyAmount` of `toToken`
/// This function exists on later versions of Curve (USDC/DAI/USDT)
/// @param i The token index being sold.
/// @param j The token index being bought.
/// @param buyAmount The amount of token being bought.
function get_dx_underlying(int128 i, int128 j, uint256 buyAmount) external returns (uint256 dx);
/// @dev Get the underlying token address from the token index
/// @param i The token index.
function underlying_coins(int128 i) external returns (address tokenAddress);
}

View File

@ -0,0 +1,33 @@
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
interface IGMX {
function getMaxAmountIn(IVault _vault, address _tokenIn, address _tokenOut) external view returns (uint256);
function getAmountOut(
IVault _vault,
address _tokenIn,
address _tokenOut,
uint256 _amountIn
) external view returns (uint256, uint256);
}
interface IVault {
function getFeeBasisPoints(
address _token,
uint256 _usdgDelta,
uint256 _feeBasisPoints,
uint256 _taxBasisPoints,
bool _increment
) external view returns (uint256);
function stableSwapFeeBasisPoints() external view returns (uint256);
function stableTokens(address _token) external view returns (bool);
function tokenDecimals(address _token) external view returns (uint256);
function getMaxPrice(address _token) external view returns (uint256);
function getMinPrice(address _token) external view returns (uint256);
}

View File

@ -0,0 +1,66 @@
pragma solidity >=0.6;
pragma experimental ABIEncoderV2;
interface IKyberElasticFactory {
/// @notice Returns the pool address for a given pair of tokens and a swap fee
/// @dev Token order does not matter
/// @param tokenA Contract address of either token0 or token1
/// @param tokenB Contract address of the other token
/// @param swapFeeUnits Fee to be collected upon every swap in the pool, in fee units
/// @return pool The pool address. Returns null address if it does not exist
function getPool(address tokenA, address tokenB, uint24 swapFeeUnits) external view returns (address pool);
}
interface IKyberElasticPool {
function token0() external view returns (address);
function token1() external view returns (address);
/// @notice The fee to be charged for a swap in basis points
/// @return The swap fee in basis points
function swapFeeUnits() external view returns (uint24);
/// @notice Look up information about a specific tick in the pool
/// @param tick The tick to look up
/// @return liquidityGross total liquidity amount from positions that uses this tick as a lower or upper tick
/// liquidityNet how much liquidity changes when the pool tick crosses above the tick
/// feeGrowthOutside the fee growth on the other side of the tick relative to the current tick
/// secondsPerLiquidityOutside the seconds spent on the other side of the tick relative to the current tick
function ticks(
int24 tick
)
external
view
returns (
uint128 liquidityGross,
int128 liquidityNet,
uint256 feeGrowthOutside,
uint128 secondsPerLiquidityOutside
);
/// @notice Returns the previous and next initialized ticks of a specific tick
/// @dev If specified tick is uninitialized, the returned values are zero.
/// @param tick The tick to look up
function initializedTicks(int24 tick) external view returns (int24 previous, int24 next);
/// @notice Fetches the pool's prices, ticks and lock status
/// @return sqrtP sqrt of current price: sqrt(token1/token0)
/// @return currentTick pool's current tick
/// @return nearestCurrentTick pool's nearest initialized tick that is <= currentTick
/// @return locked true if pool is locked, false otherwise
function getPoolState()
external
view
returns (uint160 sqrtP, int24 currentTick, int24 nearestCurrentTick, bool locked);
/// @notice Fetches the pool's liquidity values
/// @return baseL pool's base liquidity without reinvest liqudity
/// @return reinvestL the liquidity is reinvested into the pool
/// @return reinvestLLast last cached value of reinvestL, used for calculating reinvestment token qty
function getLiquidityState() external view returns (uint128 baseL, uint128 reinvestL, uint128 reinvestLLast);
}
interface IERC20 {
/// @notice Returns the amount of tokens owned by `account`.
function balanceOf(address account) external view returns (uint256);
}

View File

@ -1,17 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2023 ZeroEx Intl.
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
contract TestNoEthRecipient {}
interface IMStable {
function getSwapOutput(
address _input,
address _output,
uint256 _quantity
) external view returns (uint256 swapOutput);
}

Some files were not shown because too many files have changed in this diff Show More