Commit ef8d7a06 authored by Lawrence Forman's avatar Lawrence Forman
Browse files

switch to JS

use truffle and web3 instead of @0x tooling
remove margin trading example
parent 80412076
ETHEREUM_RPC_URL=https://kovan.infura.io/v3/ACCESS_TOKEN
CHAIN_ID=42
MNEMONIC='super secret words'
\ No newline at end of file
generated-artifacts/*.json
generated-wrappers/*.ts
ganache.log
build/*
**.log
node_modules
.env
\ No newline at end of file
MIT License
Copyright (c) 2020 ZeroEx Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
......@@ -2,41 +2,82 @@
# Get started with 0x API
This is a repo containing code snippets to interact with 0x API through smart contracts
or with web3. A number of the code snippets have corresponding guides that provides
color to what is happening in the snippets.
This is a repo containing toy examples of filling 0x-API quotes either directly with web3 or through a smart contract.
## Finding finished code snippets
On `master` branch all of the snippets are empty and will require following the guides
to complete them; to find the finished snippets, check out the `finished` branch.
## Installation
Clone this repo then, from inside the project, run:
```bash
yarn -D
# or
npm install --dev
```
This will also compile the contracts in `contracts/` and produce artifacts in `build/`.
## Examples
The following example scripts are included:
| Script | Guide | Description |
|--------|-------|-------------|
| `src/direct-swap.js` | [Swap tokens with 0x API](https://0x.org/docs/guides/swap-tokens-with-0x-api) | Perform a token swap with web3. |
| `src/swap-contract.js` | [Use 0x API liquidity in your smart contracts](https://0x.org/docs/guides/use-0x-api-liquidity-in-your-smart-contracts) | Perform a token swap in a smart contract. |
## Running the snippets
Before running the scripts run:
### Running the examples locally (forked mainnet)
The examples can be run locally (without actually mining transactions) through the magic of ganache forking. You will first need to start a forked ganache instance with the following command, replacing `ETHEREUM_RPC_URL` with the HTTP or websocket RPC URL of your mainnet ethereum node (e.g., Infura mainnet):
```bash
RPC_URL=ETHEREUM_RPC_URL npm run start-fork
```
#### Direct swap
To run the direct swap example, in a separate terminal run:
```bash
npm run swap-fork
```
$ yarn
#### Contract swap
To run the contract swap example, in a seperate terminal run:
```bash
npm run deploy-fork # Only need to do this once per ganache instance.
npm run swap-contract-fork
```
### Running the examples on mainnet
You can also run the examples and perform actual swaps that will get mined with a little effort:
1. Modify the `mnemonic` in `package.json` to one only you know.
2. Fund the first HD wallet account associated with that mnemonic with some ETH. You can run `npm run print-hd-wallet-accounts` to list the addresses associated with the configured mnemonic.
#### Direct swap
To run the direct swap example *live*, run the following:
```bash
# Replace ETHEREUM_RPC_URL with your mainnet node RPC endpoint.
RPC_URL=ETHEREUM_RPC_URL npm run swap-live
# You can also configure how much WETH is sold with the -a option, e.g.
RPC_URL=ETHEREUM_RPC_URL npm run swap-live -a 0.1
```
$ yarn build:contracts
#### Contract swap
To run the contract swap example *live*, first deploy the contract to mainnet.
```bash
# Replace ETHEREUM_RPC_URL with your mainnet node RPC endpoint.
RPC_URL=ETHEREUM_RPC_URL npm run deploy-live --network main
```
This codebase uses `ts-node` to run the typescript code-snippets.
Note the address to which the contract has been deployed. You can now run the script to perform the swap.
## Snippets
All code snippets can be found in either `examples/` or `contracts/` (dependent to if there is any smart contract code needed for the snippet)
```bash
# Replace ETHEREUM_RPC_URL with your mainnet node RPC endpoint
# and CONTRACT_ADDRESS with the deployed address of the contract.
RPC_URL=ETHEREUM_RPC_URL npm run swap-contract-live CONTRACT_ADDRESS
# You can also configure how much WETH is sold with the -a option, e.g.
RPC_URL=ETHEREUM_RPC_URL npm run swap-contract-live -a 0.1 CONTRACT_ADDRESS
```
Keep in mind that tokens will remain in the contract after the swap and can only be retrieved by your first HD wallet account through `withdrawToken()` or `withdrawETH()`.
| Main code script in `examples/` | Corresponding Guide | Description |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `web3_simple_token_swap.ts` | [Swap tokens with 0x API](https://0x.org/docs/guides/swap-tokens-with-0x-api) | Perform a token swap with web3. |
| `smart_contract_token_swap.ts` | [Use 0x API liquidity in your smart contracts](https://0x.org/docs/guides/use-0x-api-liquidity-in-your-smart-contracts) | Perform a token swap in a smart contract. |
| `smart_contract_margin_trading.ts` | [Develop a margin trading smart contract with 0x API](https://0x.org/docs/guides/develop-a-margin-trading-smart-contract-with-0x-api) | Develop a margin trading contract with Compound Finance + 0x. |
## Need help?
* Refer to our [0x API specification](https://0x.org/docs/api) for detailed documentation.
* 0x API is open source! Look through the [codebase](https://github.com/0xProject/0x-api) and deploy your own 0x API instance.
* Don’t hesitate to reach out on [Discord](https://discordapp.com/invite/d3FTX3M) for help! The 0x Core team is active on Discord to help teams building with all things 0x.
## 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
{
"artifactsDir": "./generated-artifacts",
"contractsDir": "./contracts",
"useDockerisedSolc": true,
"isOfflineMode": false,
"compilerSettings": {
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 1000000,
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.8.0;
// This contract has to exist or else truffle will complain.
contract Migrations {
address public owner = msg.sender;
uint public last_completed_migration;
modifier restricted() {
require(
msg.sender == owner,
"This function is restricted to the contract's owner"
);
_;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
}
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
// libraries
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
// interfaces
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "./interfaces/ICERC20.sol";
import "./interfaces/ICEther.sol";
import "./interfaces/IComptroller.sol";
contract SimpleMarginTrading
{
using LibSafeMath for uint256;
// constants
uint256 constant internal MAX_UINT = uint256(-1);
// contract references
address payable internal owner;
IExchange internal exchange;
IComptroller internal comptroller;
ICERC20 internal cdai;
ICEther internal ceth;
IEtherToken internal weth;
IERC20Token internal dai;
// margin position related variables
uint256 internal positionBalance = 0; // total position size (ETH locked in ceth + weth + contract balance)
// structs
struct ZeroExQuote {
address buyToken;
address sellToken;
uint256 buyAmount;
uint256 sellAmount;
uint256 protocolFee;
bytes calldataHex;
}
constructor (
address _exchange,
address _comptroller,
address _cdai,
address _dai,
address payable _ceth,
address _weth
)
public
{
exchange = IExchange(_exchange);
comptroller = IComptroller(_comptroller);
cdai = ICERC20(_cdai);
ceth = ICEther(_ceth);
weth = IEtherToken(_weth);
dai = IERC20Token(_dai);
owner = msg.sender;
// Enter markets
_enterMarkets();
}
// receive ETH
function () external payable {
}
// modifiers
modifier onlyOwner() {
require(msg.sender == owner, "permission denied");
_;
}
modifier onlyWhenClosed() {
require(positionBalance == 0, "position not closed");
_;
}
modifier onlyWhenOpen() {
require(positionBalance != 0, "position not open");
_;
}
function _enterMarkets()
internal
{
address[] memory markets = new address[](2);
markets[0] = address(ceth);
markets[1] = address(cdai);
uint[] memory errors = comptroller.enterMarkets(markets);
require(errors[0] == 0, "ceth cant enter market");
require(errors[1] == 0, "cdai cant enter market");
}
function _getZeroExApprovalAddress()
internal
view
returns (address)
{
bytes4 erc20ProxyId = IAssetData(address(0)).ERC20Token.selector;
return exchange.getAssetProxy(erc20ProxyId);
}
function _approve(address token, address delegated)
internal
{
LibERC20Token.approve(token, delegated, MAX_UINT);
}
function _executeAndVerifyZeroExSwap(ZeroExQuote memory quote)
internal
returns (LibFillResults.FillResults memory fillResults)
{
uint256 sellTokenBalanceBefore = IERC20Token(quote.sellToken).balanceOf(address(this));
uint256 buyTokenBalanceBefore = IERC20Token(quote.buyToken).balanceOf(address(this));
(bool success, bytes memory data) = address(exchange).call.value(quote.protocolFee)(quote.calldataHex);
require(success, "Swap not filled");
fillResults = abi.decode(data, (LibFillResults.FillResults));
uint256 sellTokenBalanceAfter = IERC20Token(quote.sellToken).balanceOf(address(this));
uint256 buyTokenBalanceAfter = IERC20Token(quote.buyToken).balanceOf(address(this));
// check the balances of the respective sell and buy token balances changed as intended by executing the swap.
require(sellTokenBalanceBefore.safeSub(sellTokenBalanceAfter) == fillResults.takerAssetFilledAmount, "Swap performed did not sell correct token/token amount");
require(buyTokenBalanceAfter.safeSub(buyTokenBalanceBefore) == fillResults.makerAssetFilledAmount, "Swap performed did not sell correct token/token amount");
}
function open(ZeroExQuote memory quote)
public
payable
onlyOwner
onlyWhenClosed
returns (uint256 totalPositionBalance, uint256 borrowBalance)
{
// 1. increase position by msg.value - protocolFee
positionBalance = msg.value.safeSub(quote.protocolFee);
// 2. mint collateral in compound
ceth.mint.value(positionBalance)();
// 3. borrow token
require(cdai.borrow(quote.sellAmount) == 0, "Failed to borrow cDAI from Compound Finance");
// 4. approve 0x exchange to move DAI
_approve(address(dai), _getZeroExApprovalAddress());
// 5. execute and verify swap
require(quote.sellToken == address(weth), "Provided quote is not selling WEth");
require(quote.buyToken == address(dai), "Provided quote is not buying Dai");
LibFillResults.FillResults memory fillResults = _executeAndVerifyZeroExSwap(quote);
// 6. position size increase by bought amount of WETH
positionBalance = positionBalance.safeAdd(fillResults.makerAssetFilledAmount);
totalPositionBalance = positionBalance;
borrowBalance = cdai.borrowBalanceCurrent(address(this));
// at this point you have cETH, and swapped for WETH
}
function close(
ZeroExQuote memory quote
)
public
payable
onlyOwner
onlyWhenOpen
returns (uint ethBalance)
{
// 1. wrap extra ETH sent with tx to buy DAI (additional ETH is sent to pay for interest rate)
weth.deposit.value(msg.value.safeSub(quote.protocolFee))();
// 2. approve 0x exchange to move WETH
_approve(address(weth), _getZeroExApprovalAddress());
// 3. verify swap amounts are enough to repay balance
uint256 wethBalance = weth.balanceOf(address(this));
uint256 daiBorrowBalance = cdai.borrowBalanceCurrent(address(this));
require(quote.sellToken == address(weth), "Provided quote is not selling WEth");
require(quote.buyToken == address(dai), "Provided quote is not buying Dai");
require(wethBalance >= quote.sellAmount, "Provided quote exceeds available WETH");
require(daiBorrowBalance <= quote.buyAmount, "Provided quote doesn't provide sufficient liquidity");
// 4. execute and verify swap
_executeAndVerifyZeroExSwap(quote);
// 5. return back DAI
_approve(address(dai), address(cdai));
require(cdai.repayBorrow(daiBorrowBalance) == 0, "Repayment of DAI to Compound Finance failed");
// 6. get back ETH
require(ceth.redeem(ceth.balanceOf(address(this))) == 0, "Withdrawal of ETH from Compound Finance failed");
// 7. withdraw all WETH Balance;
weth.withdraw(weth.balanceOf(address(this)));
// 8. transfer all ETH back to owner;
ethBalance = address(this).balance;
owner.transfer(address(this).balance);
// 9. reset balance
positionBalance = 0;
}
// handy function to get borrowed dai amount
function getBorrowBalance()
public
onlyOwner
returns (uint256)
{
return cdai.borrowBalanceCurrent(address(this));
}
}
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.3;
// libraries
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
// A partial ERC20 interface.
interface IERC20 {
function balanceOf(address owner) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
}
// A partial WETH interfaec.
interface IWETH is IERC20 {
function deposit() external payable;
}
// interfaces
import "@0x/contracts-exchange-forwarder/contracts/src/interfaces/IForwarder.sol";
// Demo contract that swaps its ERC20 balance for another ERC20.
// NOT to be used in production.
contract SimpleTokenSwap {
contract SimpleTokenSwap
{
using LibBytes for bytes;
event BoughtTokens(IERC20 sellToken, IERC20 buyToken, uint256 boughtAmount);
address internal owner;
IForwarder internal forwarder;
// The WETH contract.
IWETH public immutable WETH;
// Creator of this contract.
address public owner;
constructor (address _forwarder)
public
{
forwarder = IForwarder(_forwarder);
constructor(IWETH weth) {
WETH = weth;
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "permission denied");
require(msg.sender == owner, "ONLY_OWNER");
_;
}
modifier onlyERC20AssetData(bytes memory assetData) {
bytes4 proxyId = assetData.readBytes4(0);
require(proxyId == IAssetData(address(0)).ERC20Token.selector, "only ERC20 asset withdraw");
_;
// Payable fallback to allow this contract to receive protocol fee refunds.
receive() external payable {}
// Transfer tokens held by this contrat to the sender/owner.
function withdrawToken(IERC20 token, uint256 amount)
external
onlyOwner
{
require(token.transfer(msg.sender, amount));
}
function withdrawAllERC20AssetBalance(bytes memory assetData) public onlyOwner onlyERC20AssetData(assetData) {
address tokenAddress = assetData.readAddress(16);
IERC20Token tokenContract = IERC20Token(tokenAddress);
uint256 balance = tokenContract.balanceOf(address(this));
LibERC20Token.transfer(tokenAddress, msg.sender, balance);
// Transfer ETH held by this contrat to the sender/owner.
function withdrawETH(uint256 amount)
external
onlyOwner
{
msg.sender.transfer(amount);
}
function liquidityRequiringFunction(bytes memory callDataHex)
public
// Transfer ETH into this contract and wrap it into WETH.
function depositETH()
external
payable
returns (bool)
{
(bool success, bytes memory _data) = address(forwarder).call.value(msg.value)(callDataHex);
require(success == true, "swap reverted");
return success;
WETH.deposit{value: msg.value}();
}
// Swaps ERC20->ERC20 tokens held by this contract using a 0x-API quote.
function fillQuote(
// The `sellTokenAddress` field from the API response.
IERC20 sellToken,
// The `buyTokenAddress` field from the API response.
IERC20 buyToken,
// The `allowanceTarget` field from the API response.
address spender,
// The `to` field from the API response.
address payable swapTarget,
// The `data` field from the API response.
bytes calldata swapCallData
)
external
onlyOwner
payable // Must attach ETH equal to the `value` field from the API response.
{
// Track our balance of the buyToken to determine how much we've bought.
uint256 boughtAmount = buyToken.balanceOf(address(this));
// Give `spender` an infinite allowance to spend this contract's `sellToken`.
// Note that for some tokens (e.g., USDT, KNC), you must first reset any existing
// allowance to 0 before being able to update it.
require(sellToken.approve(spender, uint256(-1)));
// Call the encoded swap function call on the contract at `swapTarget`,
// passing along any ETH attached to this function call to cover protocol fees.
(bool success,) = swapTarget.call{value: msg.value}(swapCallData);
require(success, 'SWAP_CALL_FAILED');
// Refund any unspent protocol fees to the sender.
msg.sender.transfer(address(this).balance);
// Use our current buyToken balance to determine how much we've bought.
boughtAmount = buyToken.balanceOf(address(this)) - boughtAmount;
emit BoughtTokens(sellToken, buyToken, boughtAmount);
}
}
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
// interfaces
import "./ICToken.sol";
contract ICERC20 is ICToken {
function mint(uint mintAmount) external returns (uint); // For ERC20
function repayBorrow(uint repayAmount) external returns (uint); // For ERC20
function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint); // For ERC20
function borrowBalanceCurrent(address account) external returns (uint);
}
\ No newline at end of file
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
// interfaces
import "./ICToken.sol";
contract ICEther is ICToken {
function mint() external payable; // For ETH
function repayBorrow() external payable; // For ETH
function repayBorrowBehalf(address borrower) external payable; // For ETH
function borrowBalanceCurrent(address account) external returns (uint);
}
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
// Collection of interfaces for Compound finance v2; written by Instadapp
contract ICToken {
function redeem(uint redeemTokens) external returns (uint);
function redeemUnderlying(uint redeemAmount) external returns (uint);
function borrow(uint borrowAmount) external returns (uint);
function liquidateBorrow(address borrower, uint repayAmount, address cTokenCollateral) external returns (uint);
function liquidateBorrow(address borrower, address cTokenCollateral) external payable;
function exchangeRateCurrent() external returns (uint);
function getCash() external view returns (uint);
function totalBorrowsCurrent() external returns (uint);
function borrowRatePerBlock() external view returns (uint);
function supplyRatePerBlock() external view returns (uint);
function totalReserves() external view returns (uint);
function reserveFactorMantissa() external view returns (uint);
function balanceOfUnderlying(address account) external view returns (uint);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256 balance);
function allowance(address, address) external view returns (uint);
function approve(address, uint) external;
function transfer(address, uint) external returns (bool);
function transferFrom(address, address, uint) external returns (bool);
}
\ No newline at end of file
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
contract IComptroller {
function enterMarkets(address[] calldata cTokens) external returns (uint[] memory);
function exitMarket(address cTokenAddress) external returns (uint);
function getAssetsIn(address account) external view returns (address[] memory);
function getAccountLiquidity(address account) external view returns (uint, uint, uint);
}
\ No newline at end of file
// libraries
import * as qs from 'qs';
import * as fetch from 'node-fetch';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
// utils
import { setUpWeb3, baseUnitAmount } from './utils';
import { marginTradingMigrationAsync } from '../migrations/migration';
import { ASSET_ADDRESSES } from './utils/addresses';
// wrappers
import { SimpleMarginTradingContract } from '../generated-wrappers/simple_margin_trading';
// constants