diff --git a/package.json b/package.json index 0043addc..cd7d1fe4 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "ethers": "^5.4.0" }, "devDependencies": { - "@0x/contract-artifacts-v2": "npm:@0x/contract-artifacts@^2.2.2", "@gnosis.pm/safe-contracts": "^1.3.0", "@nomicfoundation/hardhat-verify": "^2.0.1", "@nomiclabs/hardhat-ethers": "^2.2.3", diff --git a/test/e2e/0xTrade.test.ts b/test/e2e/0xTrade.test.ts deleted file mode 100644 index 75bcc30a..00000000 --- a/test/e2e/0xTrade.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json"; -import { expect } from "chai"; -import Debug from "debug"; -import { BigNumber, Contract, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import { - Order, - OrderKind, - Prices, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, -} from "../../src/ts"; - -import { deployTestContracts } from "./fixture"; -import { SimpleOrder as ZeroExSimpleOrder } from "./zero-ex"; -import * as ZeroExV2 from "./zero-ex/v2"; - -const debug = Debug("test:e2e:0xTrade"); - -describe("E2E: Can settle a 0x trade", () => { - let deployer: Wallet; - let solver: Wallet; - let trader: Wallet; - let marketMaker: Wallet; - - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - let owl: Contract; - let gno: Contract; - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - deployer, - settlement, - vaultRelayer, - wallets: [solver, trader, marketMaker], - } = deployment); - - const { authenticator, manager } = deployment; - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - - owl = await waffle.deployContract(deployer, ERC20, ["OWL", 18]); - gno = await waffle.deployContract(deployer, ERC20, ["GNO", 18]); - }); - - function generateSettlementSolution(): { - gpv2Order: Order; - zeroExOrder: ZeroExSimpleOrder; - zeroExTakerAmount: BigNumber; - clearingPrices: Prices; - gpv2OwlSurplus: BigNumber; - zeroExOwlSurplus: BigNumber; - } { - const gpv2Order = { - kind: OrderKind.BUY, - partiallyFillable: false, - buyToken: gno.address, - sellToken: owl.address, - buyAmount: ethers.utils.parseEther("1.0"), - sellAmount: ethers.utils.parseEther("130.0"), - feeAmount: ethers.utils.parseEther("10.0"), - validTo: 0xffffffff, - appData: 1, - }; - - const zeroExGnoPrice = 110; - const zeroExOrder = { - takerAddress: settlement.address, - makerAssetAddress: gno.address, - makerAssetAmount: ethers.utils.parseEther("1000.0"), - takerAssetAddress: owl.address, - takerAssetAmount: ethers.utils.parseEther("1000.0").mul(zeroExGnoPrice), - }; - const zeroExTakerAmount = gpv2Order.buyAmount.mul(zeroExGnoPrice); - - const gpv2GnoPrice = 120; - const clearingPrices = { - [owl.address]: 1, - [gno.address]: gpv2GnoPrice, - }; - - const gpv2OwlSurplus = gpv2Order.sellAmount.sub( - gpv2Order.buyAmount.mul(gpv2GnoPrice), - ); - const zeroExOwlSurplus = gpv2Order.buyAmount.mul( - gpv2GnoPrice - zeroExGnoPrice, - ); - - return { - gpv2Order, - zeroExOrder, - zeroExTakerAmount, - clearingPrices, - gpv2OwlSurplus, - zeroExOwlSurplus, - }; - } - - describe("0x Protocol v2", () => { - it("should settle an EOA trade with a 0x trade", async () => { - // Settles a market order buying 1 GNO for 120 OWL and get matched with a - // market maker using 0x orders. - - const { - gpv2Order, - zeroExOrder, - zeroExTakerAmount, - clearingPrices, - gpv2OwlSurplus, - zeroExOwlSurplus, - } = generateSettlementSolution(); - - await owl.mint(trader.address, ethers.utils.parseEther("140")); - await owl - .connect(trader) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - - const zeroEx = await ZeroExV2.deployExchange(deployer); - - await gno.mint(marketMaker.address, ethers.utils.parseEther("1000.0")); - await gno - .connect(marketMaker) - .approve(zeroEx.erc20Proxy.address, ethers.constants.MaxUint256); - - const zeroExSignedOrder = await ZeroExV2.signSimpleOrder( - marketMaker, - zeroEx.domainSeparator, - zeroExOrder, - ); - expect( - await zeroEx.exchange.isValidSignature( - zeroExSignedOrder.hash, - marketMaker.address, - zeroExSignedOrder.signature, - ), - ).to.be.true; - - const encoder = new SettlementEncoder(domainSeparator); - await encoder.signEncodeTrade(gpv2Order, trader, SigningScheme.EIP712); - encoder.encodeInteraction({ - target: owl.address, - callData: owl.interface.encodeFunctionData("approve", [ - zeroEx.erc20Proxy.address, - zeroExTakerAmount, - ]), - }); - encoder.encodeInteraction({ - target: zeroEx.exchange.address, - callData: zeroEx.exchange.interface.encodeFunctionData("fillOrder", [ - zeroExSignedOrder.order, - zeroExTakerAmount, - zeroExSignedOrder.signature, - ]), - }); - - const tx = await settlement - .connect(solver) - .settle(...encoder.encodedSettlement(clearingPrices)); - - const { gasUsed } = await tx.wait(); - debug(`gas used: ${gasUsed}`); - - expect(await gno.balanceOf(trader.address)).to.deep.equal( - ethers.utils.parseEther("1.0"), - ); - expect(await gno.balanceOf(marketMaker.address)).to.deep.equal( - ethers.utils.parseEther("999.0"), - ); - - // NOTE: The user keeps the surplus from their trade. - expect(await owl.balanceOf(trader.address)).to.deep.equal(gpv2OwlSurplus); - // NOTE: The exchange keeps the surplus from the 0x order. - expect(await owl.balanceOf(settlement.address)).to.deep.equal( - zeroExOwlSurplus.add(gpv2Order.feeAmount), - ); - }); - }); -}); diff --git a/test/e2e/zero-ex/index.ts b/test/e2e/zero-ex/index.ts deleted file mode 100644 index 5cae98f5..00000000 --- a/test/e2e/zero-ex/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { BigNumberish, BytesLike } from "ethers"; - -export interface SimpleOrder { - takerAddress: string; - makerAssetAmount: BigNumberish; - takerAssetAmount: BigNumberish; - makerAssetAddress: BytesLike; - takerAssetAddress: BytesLike; -} diff --git a/test/e2e/zero-ex/v2/index.ts b/test/e2e/zero-ex/v2/index.ts deleted file mode 100644 index e25924d3..00000000 --- a/test/e2e/zero-ex/v2/index.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { ERC20Proxy, Exchange, ZRXToken } from "@0x/contract-artifacts-v2"; -import { BigNumberish, BytesLike, Contract, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import { SimpleOrder } from ".."; -import { TypedDataDomain } from "../../../../src/ts"; - -// NOTE: Order type from: -// -export interface Order { - makerAddress: string; - takerAddress: string; - feeRecipientAddress: string; - senderAddress: string; - makerAssetAmount: BigNumberish; - takerAssetAmount: BigNumberish; - makerFee: BigNumberish; - takerFee: BigNumberish; - expirationTimeSeconds: BigNumberish; - salt: BigNumberish; - makerAssetData: BytesLike; - takerAssetData: BytesLike; -} - -const ORDER_TYPE_DESCRIPTOR = { - Order: [ - { name: "makerAddress", type: "address" }, - { name: "takerAddress", type: "address" }, - { name: "feeRecipientAddress", type: "address" }, - { name: "senderAddress", type: "address" }, - { name: "makerAssetAmount", type: "uint256" }, - { name: "takerAssetAmount", type: "uint256" }, - { name: "makerFee", type: "uint256" }, - { name: "takerFee", type: "uint256" }, - { name: "expirationTimeSeconds", type: "uint256" }, - { name: "salt", type: "uint256" }, - { name: "makerAssetData", type: "bytes" }, - { name: "takerAssetData", type: "bytes" }, - ], -}; - -export interface SignedOrder { - order: Order; - hash: BytesLike; - signature: BytesLike; -} - -function encodeErc20AssetData(tokenAddress: BytesLike): string { - // NOTE: ERC20 proxy asset data defined in: - // - - const { id, hexDataSlice } = ethers.utils; - const PROXY_ID = hexDataSlice(id("ERC20Token(address)"), 0, 4); - - return ethers.utils.hexConcat([ - PROXY_ID, - ethers.utils.defaultAbiCoder.encode(["address"], [tokenAddress]), - ]); -} - -export async function signSimpleOrder( - maker: Wallet, - domain: TypedDataDomain, - simpleOrder: SimpleOrder, -): Promise { - const order = { - ...simpleOrder, - makerAddress: maker.address, - makerAssetData: encodeErc20AssetData(simpleOrder.makerAssetAddress), - takerAssetData: encodeErc20AssetData(simpleOrder.takerAssetAddress), - - // NOTE: Unused. - expirationTimeSeconds: 0xffffffff, - salt: ethers.constants.Zero, - - // NOTE: Setting taker and sender address to `address(0)` means that the - // order can be executed (sender) against any counterparty (taker). For the - // purposes of GPv2, these need to be either `address(0)` or the settlement - // contract. - takerAddress: ethers.constants.AddressZero, - senderAddress: ethers.constants.AddressZero, - - // NOTE: Include no additional fees. I am not sure how this is used by - // market makers, but in theory this can be used to assign an additional - // fee, on top of the 0x protocol fee, to the GPv2 settlement contract. - feeRecipientAddress: ethers.constants.AddressZero, - makerFee: 0, - takerFee: 0, - }; - - const hash = ethers.utils._TypedDataEncoder.hash( - domain, - ORDER_TYPE_DESCRIPTOR, - order, - ); - - // NOTE: Use EIP-712 signing scheme for the order. The signature is just the - // ECDSA signature post-fixed with the signature scheme ID (0x02): - // - - const EIP712_SIGNATURE_ID = 0x02; - const { v, r, s } = ethers.utils.splitSignature( - await maker._signTypedData(domain, ORDER_TYPE_DESCRIPTOR, order), - ); - const signature = ethers.utils.solidityPack( - ["uint8", "bytes32", "bytes32", "uint8"], - [v, r, s, EIP712_SIGNATURE_ID], - ); - - return { order, hash, signature }; -} - -export interface Deployment { - zrxToken: Contract; - exchange: Contract; - erc20Proxy: Contract; - domainSeparator: TypedDataDomain; -} - -export async function deployExchange(deployer: Wallet): Promise { - const zrxToken = await waffle.deployContract( - deployer, - ZRXToken.compilerOutput, - ); - - const zrxAssetData = encodeErc20AssetData(zrxToken.address); - const exchange = await waffle.deployContract( - deployer, - Exchange.compilerOutput, - [zrxAssetData], - ); - - const erc20Proxy = await waffle.deployContract( - deployer, - ERC20Proxy.compilerOutput, - ); - - await erc20Proxy.addAuthorizedAddress(exchange.address); - await exchange.registerAssetProxy(erc20Proxy.address); - - return { - zrxToken, - exchange, - erc20Proxy, - // NOTE: Domain separator parameters taken from: - // - domainSeparator: { - name: "0x Protocol", - version: "2", - verifyingContract: exchange.address, - }, - }; -} diff --git a/yarn.lock b/yarn.lock index 43e1761b..affcaeda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@0x/contract-artifacts-v2@npm:@0x/contract-artifacts@^2.2.2": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@0x/contract-artifacts/-/contract-artifacts-2.2.2.tgz#e6d771afb58d0b59c19c5364af5a42a3dfd17219" - integrity sha512-sbFnSXE6PlmYsbPXpKtEOR3YdVlSn63HhbPgQB3J5jm27wwQtnZ2Lf21I7BdiRBsHwwbf75C/s2pjNqafaRrgQ== - "@babel/code-frame@^7.0.0": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"