contract-deployment – Debugging 0x Exchange v3 (Execution Reverted, More Details Inside)

0xcontract-deploymentdapp-debuggingdecentralized-exchangesignature

I am trying to swap the erc721 token for erc20 using the 0x protocol which I deployed on the testnet.

Using the following script to perform the swap:

/**
 * Local, Data & Env imports
 * Exchange.json (from vendorArtifacts attached with this gist)
 * "@0x/contract-artifacts": "^3.15.0"
 */

const addr = require('./deployments.json')
const artifacts = require('@0x/contract-artifacts')
const vendorArtifacts = require('./vendor')
const dotenv = require('dotenv')

/**
 * 0x and ethers imports
 *
 * "ethers": "^5.4.6"
 * "@0x/contract-wrappers": "^13.17.6"
 * "@0x/subproviders": "^6.6.0"
 * "@0x/order-utils": "^10.4.28"
 */

const { providers, Wallet, ContractFactory, Contract, BigNumber } = require('ethers')
const { ContractWrappers } = require('@0x/contract-wrappers')
const { PrivateKeyWalletSubprovider, RPCSubprovider, Web3ProviderEngine } = require('@0x/subproviders')
const OrderUtils = require('@0x/order-utils')

dotenv.config()

/**
 * chainID - directly obtaining from connected provider to use relevant deployment addresses
 * providerEthers - General Ethers JSON-RPC provider to create maker and taker signer
 * wrapper - 0x ContractWrappers object
 * makerEthers - Signer object connected to maker's private key
 * takerEthers - Signer Object connected to taker's private key
 * engineMaker - RPC & maker private key provider for 0x
 * engineTaker - RPC & taker private key provider for 0x
 */

let chainId, providerEthers, wrapper
let makerEthers, takerEthers, engineMaker, engineTaker

const main = async () => {

    await init()

    const exchange = new Contract(addr[chainId].exchange, vendorArtifacts.Exchange.compilerOutput.abi, takerEthers)
    const devUtils = new Contract(addr[chainId].devUtils, artifacts.DevUtils.compilerOutput.abi, providerEthers)

    const erc20 = await deployERC20(takerEthers)
    const erc721 = await deployERC721(makerEthers)

    /**
     * approving erc20 and erc721 to their specific asset proxies
     * @notice proxies are already authorized to exchange v3 contract used above
     */
    await approveToProxies(
        erc20,
        BigNumber.from('1000000000000000000000000000'),
        erc721,
        1,
        addr[chainId]
    )

    wrapper = new ContractWrappers(engineTaker, {
        chainId: chainId
    })

    /**
     * @notice not using BigNumbers in order because will cause error when signing: "does not confront to order schema"
     */
    const order = {
        makerAddress: process.env.MAKER_FROM_ADDRESS,
        takerAddress: process.env.TAKER_FROM_ADDRESS,
        senderAddress: process.env.TAKER_FROM_ADDRESS,
        feeRecipientAddress: '0x0000000000000000000000000000000000000000',
        makerAssetAmount: '1',
        takerAssetAmount: '10000000000000',
        makerFee: '0',
        takerFee: '0',
        expirationTimeSeconds: '1947301711',
        salt: '510',
        makerAssetData: await devUtils.encodeERC721AssetData(erc721.address, BigNumber.from(1)),
        takerAssetData: await devUtils.encodeERC20AssetData(erc20.address),
        makerFeeAssetData: '0x',
        takerFeeAssetData: '0x',
        chainId: chainId,
        exchangeAddress: addr[chainId].exchange,
    }

    const sOrder = await OrderUtils.signatureUtils.ecSignOrderAsync(engineMaker, order, process.env.MAKER_FROM_ADDRESS)

    const valid = await wrapper.exchange.isValidOrderSignature(order, sOrder.signature).callAsync({
        to: addr[chainId].exchange
    })
    if (valid === true) console.log('- Maker signed order successfully')

    const abiEncoded = await wrapper.exchange.fillOrder(order, BigNumber.from('1'), sOrder.signature).getABIEncodedTransactionData()

    const output = await exchange.executeTransaction(
        ['510', '1947301711', '2000000000', process.env.MAKER_FROM_ADDRESS, abiEncoded],
        sOrder.signature,
        {
            gasLimit: 7000000,
            gasPrice: 2000000000,
            type: 1
        }
    )
    console.log(output)
}

const init = async () => {

    engineMaker = new Web3ProviderEngine()
    engineMaker.addProvider(new PrivateKeyWalletSubprovider(process.env.MAKER_PRIVATE_KEY))
    engineMaker.addProvider(new RPCSubprovider(process.env.RPC));
    engineMaker.start();

    engineTaker = new Web3ProviderEngine()
    engineTaker.addProvider(new PrivateKeyWalletSubprovider(process.env.TAKER_PRIVATE_KEY))
    engineTaker.addProvider(new RPCSubprovider(process.env.RPC));
    engineTaker.start();

    providerEthers = new providers.JsonRpcProvider(process.env.RPC)
    chainId = await (await providerEthers.getNetwork()).chainId

    makerEthers = new Wallet(process.env.MAKER_PRIVATE_KEY, providerEthers)
    takerEthers = new Wallet(process.env.TAKER_PRIVATE_KEY, providerEthers)

    console.log('- Providers Initialized Successfully')
}

const deployERC20 = async (signer) => {
    const erc20Factory = new ContractFactory(
        artifacts.DummyERC20Token.compilerOutput.abi,
        artifacts.DummyERC20Token.compilerOutput.evm.bytecode.object,
        signer
    )
    const erc20 = await erc20Factory.deploy(
        'Test ERC20',
        'TST',
        18,
        BigNumber.from('1000000000000000000000000000')
    )
    console.log('- ERC20 deployed successfully')
    return erc20
}

const deployERC721 = async (signer) => {
    const erc721Factory = new ContractFactory(
        artifacts.DummyERC721Token.compilerOutput.abi,
        artifacts.DummyERC721Token.compilerOutput.evm.bytecode.object,
        signer
    )
    const erc721 = await erc721Factory.deploy(
        'Test ERC721',
        'TST'
    )
    await erc721.mint(signer.address, '1')
    console.log('- ERC721 deployed successfully')
    return erc721
}

const approveToProxies = async (erc20, amount, erc721, tokenId, addrs) => {
    await erc20.approve(addrs.erc20Proxy, amount)
    await erc721.approve(addrs.erc721Proxy, tokenId)
    console.log('- Approved to proxies successfully')
}

main()

But the transaction is getting reverted each time after using 50k gas (you can check all failed attempts here: https://mumbai.polygonscan.com/address/0xAc63b037242D498483C8ad18A0A71aE56e939C22 , source code is not verified on polygonscan since it is causing trouble because of usage of abi encoder v2 experimental)

I have tried different signature mechansims for signing order (eth_sign, sign typed v3 & v4), tried officially deployed 0x v3 contracts on kovan (https://0x.org/docs/guides/0x-cheat-sheet), changing order parameters, swapping maker and taker, etc.

I have also tried to match my approach with 0x protocol's integrations test to make sure everything is correct.

Here are env & deployment files to reproduce to complete environment: https://gist.github.com/hack3r-0m/71102da691183574222fcb90241a07e8

Any help would be highly appericiated, thanks!

Best Answer

It sounds like you want to just fill an order but your code is attempting to do it through meta transactions, which is what executeTransaction() does.

To execute a fill through a meta-transaction, the taker will need to sign the meta-transaction object as well, and that is the signature that goes into executeTransaction(). @0x/order-utils has helpers for this as well.

To fill an order directly, the taker just has to call fillOrder() on the exchange contract. You are also encoding the fillOrder() call for too low a taker asset fill amount. I think you mean to pass in 10000000000000, not 1, because anything less than 10000000000000 will round down to 0 of the maker asset, due to the exchange rate the order specifies.

Related Topic