diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f8b25ba..cc84c7f5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,5 +66,9 @@ jobs: - name: Run Forge tests if: matrix.profile != 'solc-0.7.6' run: | + if [ "${{ matrix.profile }}" == "default" ]; then + echo "Setting FORK_URL for default profile" + export FORK_URL="${{ secrets.MAINNET_ARCHIVE_RPC }}" + fi FOUNDRY_PROFILE=ci forge test -vvv id: test diff --git a/foundry.toml b/foundry.toml index ef318bbb..a0113718 100644 --- a/foundry.toml +++ b/foundry.toml @@ -14,7 +14,7 @@ deny_warnings = true fs_permissions = [ { access = "read", path = "./balancer"}, { access = "read", path = "./networks.json"}, - { access = "read", path = "./out"} + { access = "read", path = "./out"}, ] [fmt] diff --git a/package.json b/package.json index 0043addc..bff5f59f 100644 --- a/package.json +++ b/package.json @@ -36,16 +36,10 @@ "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", "@nomiclabs/hardhat-waffle": "^2.0.5", "@openzeppelin/contracts": "=3.4.0-solc-0.7", - "@safe-global/api-kit": "^1.3.0", - "@safe-global/protocol-kit": "^1.2.0", - "@safe-global/safe-core-sdk-types": "^2.2.0", - "@slack/web-api": "^6.9.0", "@tenderly/hardhat-tenderly": "~1.1.6", "@types/chai": "^4.3.4", "@types/chai-as-promised": "^7.1.5", @@ -58,7 +52,6 @@ "@typescript-eslint/parser": "^5.58.0", "@uniswap/v2-core": "^1.0.1", "@uniswap/v2-periphery": "^1.1.0-beta.0", - "axios": "^1.3.5", "canonical-weth": "^1.4.0", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", @@ -72,7 +65,6 @@ "eslint-plugin-prettier": "^4.2.1", "ethereum-waffle": "^3.4.4", "ethers": "^5.7.2", - "globby": "^11.0.4", "hardhat": "^2.13.1", "hardhat-deploy": "^0.11.26", "hardhat-gas-reporter": "^1.0.9", diff --git a/src/ts/deploy.ts b/src/ts/deploy.ts index c67a13e9..5daa4e38 100644 --- a/src/ts/deploy.ts +++ b/src/ts/deploy.ts @@ -5,15 +5,6 @@ import { utils } from "ethers"; */ export const SALT = utils.formatBytes32String("Mattresses in Berlin!"); -/** - * The contract used to deploy contracts deterministically with CREATE2. - * The address is chosen by the hardhat-deploy library. - * It is the same in any EVM-based network. - * - * https://github.com/Arachnid/deterministic-deployment-proxy - */ -export const DEPLOYER_CONTRACT = "0x4e59b44847b379578588920ca78fbf26c0b4956c"; - /** * Dictionary containing all deployed contract names. */ @@ -21,69 +12,3 @@ export const CONTRACT_NAMES = { authenticator: "GPv2AllowListAuthentication", settlement: "GPv2Settlement", } as const; - -/** - * The name of a deployed contract. - */ -export type ContractName = (typeof CONTRACT_NAMES)[keyof typeof CONTRACT_NAMES]; - -/** - * The deployment args for a contract. - */ -export type DeploymentArguments = - T extends typeof CONTRACT_NAMES.authenticator - ? never - : T extends typeof CONTRACT_NAMES.settlement - ? [string, string] - : unknown[]; - -/** - * Allowed ABI definition types by Ethers.js. - */ -export type Abi = ConstructorParameters[0]; - -/** - * Artifact information important for computing deterministic deployments. - */ -export interface ArtifactDeployment { - abi: Abi; - bytecode: string; -} - -/** - * An artifact with a contract name matching one of the deterministically - * deployed contracts. - */ -export interface NamedArtifactDeployment - extends ArtifactDeployment { - contractName: C; -} - -type MaybeNamedArtifactArtifactDeployment = C extends ContractName - ? NamedArtifactDeployment - : ArtifactDeployment; - -/** - * Computes the deterministic address at which the contract will be deployed. - * This address does not depend on which network the contract is deployed to. - * - * @param contractName Name of the contract for which to find the address. - * @param deploymentArguments Extra arguments that are necessary to deploy. - * @returns The address that is expected to store the deployed code. - */ -export function deterministicDeploymentAddress( - { abi, bytecode }: MaybeNamedArtifactArtifactDeployment, - deploymentArguments: DeploymentArguments, -): string { - const contractInterface = new utils.Interface(abi); - const deployData = utils.hexConcat([ - bytecode, - contractInterface.encodeDeploy(deploymentArguments), - ]); - - return utils.getCreate2Address( - DEPLOYER_CONTRACT, - SALT, - utils.keccak256(deployData), - ); -} diff --git a/src/ts/index.ts b/src/ts/index.ts index 7be9e239..1403e450 100644 --- a/src/ts/index.ts +++ b/src/ts/index.ts @@ -22,7 +22,6 @@ export function domain( export * from "./deploy"; export * from "./interaction"; export * from "./order"; -export * from "./proxy"; export * from "./settlement"; export * from "./sign"; export * from "./swap"; diff --git a/src/ts/interaction.ts b/src/ts/interaction.ts index d352c5b4..25ad8f28 100644 --- a/src/ts/interaction.ts +++ b/src/ts/interaction.ts @@ -36,16 +36,3 @@ export function normalizeInteraction( ...interaction, }; } - -/** - * Normalizes data for many interactions so that they can be ABI encoded. This - * calls [`normalizeInteraction`] for each interaction. - * - * @param interactions The interactions to normalize. - * @return The normalized interactions. - */ -export function normalizeInteractions( - interactions: InteractionLike[], -): Interaction[] { - return interactions.map(normalizeInteraction); -} diff --git a/src/ts/order.ts b/src/ts/order.ts index f66d665d..ad6912d9 100644 --- a/src/ts/order.ts +++ b/src/ts/order.ts @@ -72,25 +72,6 @@ export interface Order { buyTokenBalance?: OrderBalance; } -/** - * Gnosis Protocol v2 order cancellation data. - */ -export interface OrderCancellations { - /** - * The unique identifier of the order to be cancelled. - */ - orderUids: BytesLike[]; -} - -/** - * Marker address to indicate that an order is buying Ether. - * - * Note that this address is only has special meaning in the `buyToken` and will - * be treated as a ERC20 token address in the `sellToken` position, causing the - * settlement to revert. - */ -export const BUY_ETH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; - /** * Gnosis Protocol v2 order flags. */ @@ -102,12 +83,12 @@ export type OrderFlags = Pick< /** * A timestamp value. */ -export type Timestamp = number | Date; +type Timestamp = number | Date; /** * A hash-like app data value. */ -export type HashLike = BytesLike | number; +type HashLike = BytesLike | number; /** * Order kind. @@ -168,7 +149,7 @@ export const ORDER_TYPE_FIELDS = [ * @param time The timestamp value to normalize. * @return Unix timestamp or number of seconds since the Unix Epoch. */ -export function timestamp(t: Timestamp): number { +function timestamp(t: Timestamp): number { return typeof t === "number" ? t : ~~(t.getTime() / 1000); } @@ -177,7 +158,7 @@ export function timestamp(t: Timestamp): number { * @param hashLike A hash-like value to normalize. * @returns A 32-byte hash encoded as a hex-string. */ -export function hashify(h: HashLike): string { +function hashify(h: HashLike): string { return typeof h === "number" ? `0x${h.toString(16).padStart(64, "0")}` : ethers.utils.hexZeroPad(h, 32); @@ -258,21 +239,6 @@ export function hashTypedData( return ethers.utils._TypedDataEncoder.hash(domain, types, data); } -/** - * Compute the 32-byte signing hash for the specified order. - * - * @param domain The EIP-712 domain separator to compute the hash for. - * @param order The order to compute the digest for. - * @return Hex-encoded 32-byte order digest. - */ -export function hashOrder(domain: TypedDataDomain, order: Order): string { - return hashTypedData( - domain, - { Order: ORDER_TYPE_FIELDS }, - normalizeOrder(order), - ); -} - /** * The byte length of an order UID. */ @@ -281,7 +247,7 @@ export const ORDER_UID_LENGTH = 56; /** * Order unique identifier parameters. */ -export interface OrderUidParams { +interface OrderUidParams { /** * The EIP-712 order struct hash. */ @@ -296,21 +262,6 @@ export interface OrderUidParams { validTo: number | Date; } -/** - * Computes the order UID for an order and the given owner. - */ -export function computeOrderUid( - domain: TypedDataDomain, - order: Order, - owner: string, -): string { - return packOrderUidParams({ - orderDigest: hashOrder(domain, order), - owner, - validTo: order.validTo, - }); -} - /** * Compute the unique identifier describing a user order in the settlement * contract. @@ -329,25 +280,3 @@ export function packOrderUidParams({ [orderDigest, owner, timestamp(validTo)], ); } - -/** - * Extracts the order unique identifier parameters from the specified bytes. - * - * @param orderUid The order UID encoded as a hexadecimal string. - * @returns The extracted order UID parameters. - */ -export function extractOrderUidParams(orderUid: string): OrderUidParams { - const bytes = ethers.utils.arrayify(orderUid); - if (bytes.length != ORDER_UID_LENGTH) { - throw new Error("invalid order UID length"); - } - - const view = new DataView(bytes.buffer); - return { - orderDigest: ethers.utils.hexlify(bytes.subarray(0, 32)), - owner: ethers.utils.getAddress( - ethers.utils.hexlify(bytes.subarray(32, 52)), - ), - validTo: view.getUint32(52), - }; -} diff --git a/src/ts/proxy.ts b/src/ts/proxy.ts deleted file mode 100644 index 55c1875e..00000000 --- a/src/ts/proxy.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { BigNumber, BytesLike, Contract, ethers } from "ethers"; - -/** - * Compute an EIP-1967 slot for the specified name. The proxy contract used by - * `hardhat-deploy` implements EIP-1967 (Standard Proxy Storage Slot). - * - * . - */ -function slot(name: string): BytesLike { - return ethers.utils.defaultAbiCoder.encode( - ["bytes32"], - [BigNumber.from(ethers.utils.id(name)).sub(1)], - ); -} - -const IMPLEMENTATION_STORAGE_SLOT = slot("eip1967.proxy.implementation"); -const OWNER_STORAGE_SLOT = slot("eip1967.proxy.admin"); - -/** - * Returns the address of the implementation of an EIP-1967-compatible proxy - * from its address. - * - * @param proxy Address of the proxy contract. - * @returns The address of the contract storing the proxy implementation. - */ -export async function implementationAddress( - provider: ethers.providers.Provider, - proxy: string, -): Promise { - const [implementation] = ethers.utils.defaultAbiCoder.decode( - ["address"], - await provider.getStorageAt(proxy, IMPLEMENTATION_STORAGE_SLOT), - ); - return implementation; -} - -/** - * Returns the address of the implementation of an EIP-1967-compatible proxy - * from its address. - * - * @param proxy Address of the proxy contract. - * @returns The address of the administrator of the proxy. - */ -export async function ownerAddress( - provider: ethers.providers.Provider, - proxy: string, -): Promise { - const [owner] = ethers.utils.defaultAbiCoder.decode( - ["address"], - await provider.getStorageAt(proxy, OWNER_STORAGE_SLOT), - ); - return owner; -} - -/** - * EIP-173 proxy ABI in "human-readable ABI" format. The proxy used by the - * deployment plugin implements this interface, and copying it here avoids - * pulling in `hardhat` as a dependency for just this ABI. - * - * - */ -export const EIP173_PROXY_ABI = [ - "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", - "function owner() view external returns(address)", - "function transferOwnership(address newOwner) external", - "function supportsInterface(bytes4 interfaceID) external view returns (bool)", -]; - -/** - * Returns the proxy interface for the specified address. - * - * @param contract The proxy contract to return a proxy interface for. - * @returns A Ethers.js contract instance for interacting with the proxy. - */ -export function proxyInterface(contract: Contract): Contract { - return new Contract( - contract.address, - EIP173_PROXY_ABI, - contract.signer ?? contract.provider, - ); -} diff --git a/src/ts/settlement.ts b/src/ts/settlement.ts index ffc06541..65249b95 100644 --- a/src/ts/settlement.ts +++ b/src/ts/settlement.ts @@ -30,7 +30,7 @@ import { TypedDataDomain } from "./types/ethers"; /** * The stage an interaction should be executed in. */ -export enum InteractionStage { +enum InteractionStage { /** * A pre-settlement intraction. * @@ -100,7 +100,7 @@ export type Trade = TradeExecution & /** * Details representing how an order was executed. */ -export interface TradeExecution { +interface TradeExecution { /** * The executed trade amount. * @@ -123,7 +123,7 @@ export interface TradeExecution { * increased again in the future. However, order refunds should not be used in * an actual settlement. */ -export interface OrderRefunds { +interface OrderRefunds { /** Refund storage used for order filled amount */ filledAmounts: BytesLike[]; /** Refund storage used for order pre-signature */ @@ -133,12 +133,12 @@ export interface OrderRefunds { /** * Table mapping token addresses to their respective clearing prices. */ -export type Prices = Record; +type Prices = Record; /** * Encoded settlement parameters. */ -export type EncodedSettlement = [ +type EncodedSettlement = [ /** Tokens. */ string[], /** Clearing prices. */ @@ -233,7 +233,7 @@ function decodeFlag( * @param scheme The signing scheme to encode. * @return The bitfield result. */ -export function encodeSigningScheme(scheme: SigningScheme): number { +function encodeSigningScheme(scheme: SigningScheme): number { return encodeFlag("signingScheme", scheme); } @@ -243,7 +243,7 @@ export function encodeSigningScheme(scheme: SigningScheme): number { * @param flag The encoded order flag. * @return The decoded signing scheme. */ -export function decodeSigningScheme(flags: BigNumberish): SigningScheme { +function decodeSigningScheme(flags: BigNumberish): SigningScheme { return decodeFlag("signingScheme", flags); } @@ -253,7 +253,7 @@ export function decodeSigningScheme(flags: BigNumberish): SigningScheme { * @param flags The order flags to encode. * @return The bitfield result. */ -export function encodeOrderFlags(flags: OrderFlags): number { +function encodeOrderFlags(flags: OrderFlags): number { return ( encodeFlag("kind", flags.kind) | encodeFlag("partiallyFillable", flags.partiallyFillable) | @@ -274,7 +274,7 @@ export function encodeOrderFlags(flags: OrderFlags): number { * @param flags The order flags encoded as a bitfield. * @return The decoded order flags. */ -export function decodeOrderFlags(flags: BigNumberish): OrderFlags { +function decodeOrderFlags(flags: BigNumberish): OrderFlags { return { kind: decodeFlag("kind", flags), partiallyFillable: decodeFlag("partiallyFillable", flags), @@ -306,7 +306,7 @@ export function decodeTradeFlags(flags: BigNumberish): TradeFlags { }; } -export function encodeSignatureData(sig: Signature): string { +function encodeSignatureData(sig: Signature): string { switch (sig.scheme) { case SigningScheme.EIP712: case SigningScheme.ETHSIGN: diff --git a/src/ts/sign.ts b/src/ts/sign.ts index a41e04f2..828a64ab 100644 --- a/src/ts/sign.ts +++ b/src/ts/sign.ts @@ -13,17 +13,6 @@ import { TypedDataDomain, } from "./types/ethers"; -/** - * Value returned by a call to `isValidSignature` if the signature was verified - * successfully. The value is defined in the EIP-1271 standard as: - * bytes4(keccak256("isValidSignature(bytes32,bytes)")) - */ -export const EIP1271_MAGICVALUE = ethers.utils.hexDataSlice( - ethers.utils.id("isValidSignature(bytes32,bytes)"), - 0, - 4, -); - /** * The signing scheme used to sign the order. */ diff --git a/src/ts/swap.ts b/src/ts/swap.ts index 98460846..def0f9be 100644 --- a/src/ts/swap.ts +++ b/src/ts/swap.ts @@ -39,7 +39,7 @@ export interface Swap { * An encoded Balancer swap request that can be used as input to the settlement * contract. */ -export interface BatchSwapStep { +interface BatchSwapStep { /** * The ID of the pool for the swap. */ @@ -68,7 +68,7 @@ export interface BatchSwapStep { /** * Swap execution parameters. */ -export interface SwapExecution { +interface SwapExecution { /** * The limit amount for the swap. * @@ -81,7 +81,7 @@ export interface SwapExecution { /** * Encoded swap parameters. */ -export type EncodedSwap = [ +type EncodedSwap = [ /** Swap requests. */ BatchSwapStep[], /** Tokens. */ @@ -94,10 +94,7 @@ export type EncodedSwap = [ * Encodes a swap as a {@link BatchSwapStep} to be used with the settlement * contract. */ -export function encodeSwapStep( - tokens: TokenRegistry, - swap: Swap, -): BatchSwapStep { +function encodeSwapStep(tokens: TokenRegistry, swap: Swap): BatchSwapStep { return { poolId: swap.poolId, assetInIndex: tokens.index(swap.assetIn), 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/BalancerSwap.t.sol b/test/e2e/BalancerSwap.t.sol new file mode 100644 index 00000000..c26dbda9 --- /dev/null +++ b/test/e2e/BalancerSwap.t.sol @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; +import {IVault} from "src/contracts/interfaces/IVault.sol"; + +import {GPv2Order} from "src/contracts/libraries/GPv2Order.sol"; +import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol"; + +import {SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol"; +import {SwapEncoder} from "../libraries/encoders/SwapEncoder.sol"; +import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol"; +import {Helper, IERC20Mintable} from "./Helper.sol"; + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; +using SwapEncoder for SwapEncoder.State; + +interface IMockPool { + function registerTokens(IERC20[] calldata tokens, address[] calldata assetManagers) external; + function getPoolId() external view returns (bytes32); + function setMultiplier(uint256) external; +} + +interface IBalancerVault is IVault { + struct JoinPoolRequest { + IERC20[] assets; + uint256[] maxAmountsIn; + bytes userData; + bool fromInternalBalance; + } + + function joinPool(bytes32 poolId, address sender, address recipient, JoinPoolRequest calldata request) external; + function setRelayerApproval(address, address, bool) external; + function getInternalBalance(address user, IERC20[] calldata tokens) external view returns (uint256[] memory); +} + +contract BalancerSwapTest is Helper(true) { + IERC20Mintable token1; + IERC20Mintable token2; + IERC20Mintable token3; + + mapping(address => mapping(address => address)) pools; + + function setUp() public override { + super.setUp(); + + token1 = deployMintableErc20("TOK1", "TOK1"); + token2 = deployMintableErc20("TOK2", "TOK2"); + token3 = deployMintableErc20("TOK3", "TOK3"); + + Vm.Wallet memory pooler = vm.createWallet("pooler"); + + IERC20Mintable[] memory tokens = new IERC20Mintable[](3); + tokens[0] = token1; + tokens[1] = token2; + tokens[2] = token3; + + uint256 lots = 10000 ether; + + for (uint256 i = 0; i < tokens.length; i++) { + (IERC20Mintable token0_, IERC20Mintable token1_) = (tokens[i], tokens[(i + 1) % tokens.length]); + (IERC20Mintable tokenA, IERC20Mintable tokenB) = + address(token0_) < address(token1_) ? (token0_, token1_) : (token1_, token0_); + + uint256 twoTokenSpecialization = 2; + + vm.startPrank(deployer); + IMockPool pool = IMockPool( + _create( + abi.encodePacked( + vm.getCode("balancer/test/MockPool.json"), abi.encode(address(vault), twoTokenSpecialization) + ), + 0 + ) + ); + IERC20[] memory tks = new IERC20[](2); + tks[0] = tokenA; + tks[1] = tokenB; + address[] memory assetManagers = new address[](2); + assetManagers[0] = address(0); + assetManagers[1] = address(0); + pool.registerTokens(tks, assetManagers); + vm.stopPrank(); + + for (uint256 j = 0; j < tks.length; j++) { + IERC20Mintable(address(tks[j])).mint(pooler.addr, lots); + vm.prank(pooler.addr); + tks[j].approve(address(vault), type(uint256).max); + } + + uint256[] memory maxAmountsIn = new uint256[](2); + maxAmountsIn[0] = lots; + maxAmountsIn[1] = lots; + uint256[] memory poolFees = new uint256[](2); + IBalancerVault.JoinPoolRequest memory request = IBalancerVault.JoinPoolRequest({ + assets: tks, + maxAmountsIn: maxAmountsIn, + // NOTE: The mock pool uses this for encoding the pool share amounts + // that a user (here `pooler`) gets when joining the pool (first value) + // as well as the pool fees (second value). + userData: abi.encode(maxAmountsIn, poolFees), + fromInternalBalance: false + }); + // poolerAddr declared as separate var to prevent stack too deep errors + address poolerAddr = pooler.addr; + bytes32 poolId = pool.getPoolId(); + vm.prank(poolerAddr); + IBalancerVault(address(vault)).joinPool(poolId, poolerAddr, poolerAddr, request); + + pools[address(tokenA)][address(tokenB)] = address(pool); + pools[address(tokenB)][address(tokenA)] = address(pool); + } + } + + function test_reverts_if_order_is_expired() external { + _mintAndApprove(trader, token1, 100.1 ether, GPv2Order.BALANCE_ERC20); + + swapEncoder.signEncodeTrade( + vm, + trader, + GPv2Order.Data({ + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellToken: token1, + buyToken: token2, + sellAmount: 100 ether, + buyAmount: 72 ether, + feeAmount: 0.1 ether, + validTo: uint32(block.timestamp) - 1, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + SwapEncoder.EncodedSwap memory encodedSwap = swapEncoder.encode(); + vm.prank(solver); + // SWAP_DEADLINE + vm.expectRevert("BAL#508"); + swap(encodedSwap); + } + + function test_allows_using_liquidity_from_multiple_pools() external { + _mintAndApprove(trader, token1, 100.1 ether, GPv2Order.BALANCE_ERC20); + + swapEncoder.signEncodeTrade( + vm, + trader, + GPv2Order.Data({ + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellToken: token1, + buyToken: token3, + sellAmount: 100 ether, + buyAmount: 125 ether, + feeAmount: 0.1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // NOTE: Use liquidity by performing a multi-hop swap from `0 -> 1 -> 2`. + _poolFor(token1, token2).setMultiplier(1.1 ether); + swapEncoder.encodeSwapStep( + SwapEncoder.Swap({ + poolId: _poolFor(token1, token2).getPoolId(), + assetIn: token1, + assetOut: token2, + amount: 70 ether, + userData: hex"" + }) + ); + _poolFor(token2, token3).setMultiplier(1.2 ether); + swapEncoder.encodeSwapStep( + SwapEncoder.Swap({ + poolId: _poolFor(token2, token3).getPoolId(), + assetIn: token2, + assetOut: token3, + // NOTE: Setting amount to zero indicates a "multi-hop" swap and uses the + // computed `amountOut` of the previous swap. + amount: 0, + userData: hex"" + }) + ); + // NOTE: Also use liquidity from a direct `0 -> 2` pool. + _poolFor(token1, token3).setMultiplier(1.3 ether); + swapEncoder.encodeSwapStep( + SwapEncoder.Swap({ + poolId: _poolFor(token1, token3).getPoolId(), + assetIn: token1, + assetOut: token3, + amount: 30 ether, + userData: hex"" + }) + ); + + SwapEncoder.EncodedSwap memory encodedSwap = swapEncoder.encode(); + vm.prank(solver); + swap(encodedSwap); + + // NOTE: Sold 70 for 1.1*1.2 and 30 for 1.3, so should receive 131.4. + assertEq( + _balanceOf(trader.addr, token3, GPv2Order.BALANCE_ERC20), + 131.4 ether, + "multihop swap output not as expected" + ); + } + + function test_allows_multi_hop_buy_orders() external { + _mintAndApprove(trader, token1, 13.1 ether, GPv2Order.BALANCE_ERC20); + + swapEncoder.signEncodeTrade( + vm, + trader, + GPv2Order.Data({ + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellToken: token1, + buyToken: token3, + sellAmount: 13 ether, + buyAmount: 100 ether, + feeAmount: 0.1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // NOTE: Use liquidity by performing a multi-hop swap from `2 -> 1 -> 0`. + _poolFor(token3, token2).setMultiplier(4 ether); + swapEncoder.encodeSwapStep( + SwapEncoder.Swap({ + poolId: _poolFor(token3, token2).getPoolId(), + assetOut: token3, + assetIn: token2, + amount: 100 ether, + userData: hex"" + }) + ); + _poolFor(token2, token1).setMultiplier(2 ether); + swapEncoder.encodeSwapStep( + SwapEncoder.Swap({ + poolId: _poolFor(token2, token1).getPoolId(), + assetOut: token2, + assetIn: token1, + // NOTE: Setting amount to zero indicates a "multi-hop" swap and uses the + // computed `amountIn` of the previous swap. + amount: 0, + userData: hex"" + }) + ); + + SwapEncoder.EncodedSwap memory encodedSwap = swapEncoder.encode(); + vm.prank(solver); + swap(encodedSwap); + + // NOTE: Bought 100 for 4.0*2.0, so should pay 12.5. + assertEq( + _balanceOf(trader.addr, token1, GPv2Order.BALANCE_ERC20), 0.5 ether, "multihop swap output not as expected" + ); + } + + function test_performs_balancer_swap_for_erc20_to_erc20_sell_order() external { + _testBalancerSwap(GPv2Order.BALANCE_ERC20, GPv2Order.BALANCE_ERC20, GPv2Order.KIND_SELL); + } + + function test_performs_balancer_swap_for_erc20_to_internal_sell_order() external { + _testBalancerSwap(GPv2Order.BALANCE_ERC20, GPv2Order.BALANCE_INTERNAL, GPv2Order.KIND_SELL); + } + + function test_performs_balancer_swap_for_internal_to_erc20_sell_order() external { + _testBalancerSwap(GPv2Order.BALANCE_INTERNAL, GPv2Order.BALANCE_ERC20, GPv2Order.KIND_SELL); + } + + function test_performs_balancer_swap_for_internal_to_internal_sell_order() external { + _testBalancerSwap(GPv2Order.BALANCE_INTERNAL, GPv2Order.BALANCE_INTERNAL, GPv2Order.KIND_SELL); + } + + function test_performs_balancer_swap_for_external_to_erc20_sell_order() external { + _testBalancerSwap(GPv2Order.BALANCE_EXTERNAL, GPv2Order.BALANCE_ERC20, GPv2Order.KIND_SELL); + } + + function test_performs_balancer_swap_for_external_to_internal_sell_order() external { + _testBalancerSwap(GPv2Order.BALANCE_EXTERNAL, GPv2Order.BALANCE_INTERNAL, GPv2Order.KIND_SELL); + } + + function test_performs_balancer_swap_for_erc20_to_erc20_buy_order() external { + _testBalancerSwap(GPv2Order.BALANCE_ERC20, GPv2Order.BALANCE_ERC20, GPv2Order.KIND_BUY); + } + + function test_performs_balancer_swap_for_erc20_to_internal_buy_order() external { + _testBalancerSwap(GPv2Order.BALANCE_ERC20, GPv2Order.BALANCE_INTERNAL, GPv2Order.KIND_BUY); + } + + function test_performs_balancer_swap_for_internal_to_erc20_buy_order() external { + _testBalancerSwap(GPv2Order.BALANCE_INTERNAL, GPv2Order.BALANCE_ERC20, GPv2Order.KIND_BUY); + } + + function test_performs_balancer_swap_for_internal_to_internal_buy_order() external { + _testBalancerSwap(GPv2Order.BALANCE_INTERNAL, GPv2Order.BALANCE_INTERNAL, GPv2Order.KIND_BUY); + } + + function test_performs_balancer_swap_for_external_to_erc20_buy_order() external { + _testBalancerSwap(GPv2Order.BALANCE_EXTERNAL, GPv2Order.BALANCE_ERC20, GPv2Order.KIND_BUY); + } + + function test_performs_balancer_swap_for_external_to_internal_buy_order() external { + _testBalancerSwap(GPv2Order.BALANCE_EXTERNAL, GPv2Order.BALANCE_INTERNAL, GPv2Order.KIND_BUY); + } + + function test_reverts_sell_order_if_fill_or_kill_is_not_respected() external { + _testBalancerRevertFillOrKill(GPv2Order.KIND_SELL); + } + + function test_reverts_buy_order_if_fill_or_kill_is_not_respected() external { + _testBalancerRevertFillOrKill(GPv2Order.KIND_BUY); + } + + function test_reverts_sell_order_if_limit_price_is_not_respected() external { + _testBalancerRevertLimitPrice(GPv2Order.KIND_SELL); + } + + function test_reverts_buy_order_if_limit_price_is_not_respected() external { + _testBalancerRevertLimitPrice(GPv2Order.KIND_BUY); + } + + function _testBalancerSwap(bytes32 sellSource, bytes32 buySource, bytes32 orderKind) internal { + _mintAndApprove(trader, token1, 100.1 ether, sellSource); + + IMockPool pool = _poolFor(token1, token2); + // NOTE: Set a fixed multiplier used for computing the exchange rate for + // the mock pool. In the wild, this would depend on the current state of + // the pool. + pool.setMultiplier(0.9 ether); + + swapEncoder.signEncodeTrade( + vm, + trader, + GPv2Order.Data({ + kind: orderKind, + partiallyFillable: false, + sellToken: token1, + buyToken: token2, + sellAmount: 100 ether, + buyAmount: 72 ether, + feeAmount: 0.1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: sellSource, + buyTokenBalance: buySource, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + swapEncoder.encodeSwapStep( + SwapEncoder.Swap({ + poolId: pool.getPoolId(), + assetIn: token1, + assetOut: token2, + amount: orderKind == GPv2Order.KIND_SELL ? 100 ether : 72 ether, + userData: hex"" + }) + ); + + SwapEncoder.EncodedSwap memory encodedSwap = swapEncoder.encode(); + + vm.prank(solver); + swap(encodedSwap); + + uint256 sellTokenBalance = _balanceOf(trader.addr, token1, sellSource); + uint256 buyTokenBalance = _balanceOf(trader.addr, token2, buySource); + + if (orderKind == GPv2Order.KIND_SELL) { + assertEq(sellTokenBalance, 0, "seller sellTokenBalance not as expected"); + assertEq(buyTokenBalance, 90 ether, "seller buyTokenBalance not as expected"); + } else { + assertEq(sellTokenBalance, 20 ether, "buyer sellTokenBalance not as expected"); + assertEq(buyTokenBalance, 72 ether, "buyer buyTokenBalance not as expected"); + } + } + + function _testBalancerRevertFillOrKill(bytes32 orderKind) internal { + _mintAndApprove(trader, token1, 100.1 ether, GPv2Order.BALANCE_ERC20); + + IMockPool pool = _poolFor(token1, token2); + pool.setMultiplier(2 ether); + + swapEncoder.signEncodeTrade( + vm, + trader, + GPv2Order.Data({ + kind: orderKind, + // NOTE: Partially fillable or not, it doesn't matter as the + // "fast-path" treats all orders as fill-or-kill orders. + partiallyFillable: true, + sellToken: token1, + buyToken: token2, + sellAmount: 100 ether, + buyAmount: 100 ether, + feeAmount: 0.1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + swapEncoder.encodeSwapStep( + SwapEncoder.Swap({ + poolId: pool.getPoolId(), + assetIn: token1, + assetOut: token2, + amount: orderKind == GPv2Order.KIND_SELL ? 99 ether : 101 ether, + userData: hex"" + }) + ); + + SwapEncoder.EncodedSwap memory encodedSwap = swapEncoder.encode(); + + vm.expectRevert( + bytes( + orderKind == GPv2Order.KIND_SELL ? "GPv2: sell amount not respected" : "GPv2: buy amount not respected" + ) + ); + vm.prank(solver); + swap(encodedSwap); + } + + function _testBalancerRevertLimitPrice(bytes32 orderKind) internal { + _mintAndApprove(trader, token1, 100.1 ether, GPv2Order.BALANCE_ERC20); + + IMockPool pool = _poolFor(token1, token2); + // NOTE: Set a multiplier that satisfies the order's limit price but not + // the specified limit amount. + pool.setMultiplier(1.1 ether); + + swapEncoder.signEncodeTrade( + vm, + trader, + GPv2Order.Data({ + kind: orderKind, + partiallyFillable: false, + sellToken: token1, + buyToken: token2, + sellAmount: 100 ether, + buyAmount: 100 ether, + feeAmount: 0.1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + orderKind == GPv2Order.KIND_SELL ? 120 ether : 80 ether + ); + + swapEncoder.encodeSwapStep( + SwapEncoder.Swap({ + poolId: pool.getPoolId(), + assetIn: token1, + assetOut: token2, + amount: 100 ether, + userData: hex"" + }) + ); + + SwapEncoder.EncodedSwap memory encodedSwap = swapEncoder.encode(); + + vm.expectRevert("BAL#507"); + vm.prank(solver); + swap(encodedSwap); + } + + function _mintAndApprove(Vm.Wallet memory wallet, IERC20Mintable token, uint256 amount, bytes32 balance) internal { + token.mint(wallet.addr, amount); + vm.startPrank(wallet.addr); + token.approve(vaultRelayer, type(uint256).max); + token.approve(address(vault), type(uint256).max); + vm.stopPrank(); + + if (balance == GPv2Order.BALANCE_INTERNAL) { + vm.prank(wallet.addr); + IVault.UserBalanceOp[] memory ops = new IVault.UserBalanceOp[](1); + ops[0] = IVault.UserBalanceOp({ + kind: IVault.UserBalanceOpKind.DEPOSIT_INTERNAL, + asset: token, + amount: amount, + sender: wallet.addr, + recipient: payable(wallet.addr) + }); + vault.manageUserBalance(ops); + } + vm.prank(wallet.addr); + IBalancerVault(address(vault)).setRelayerApproval(wallet.addr, vaultRelayer, true); + } + + function _poolFor(IERC20 tk0, IERC20 tk1) internal view returns (IMockPool) { + return IMockPool(pools[address(tk0)][address(tk1)]); + } + + function _balanceOf(address user, IERC20 tk, bytes32 balance) internal view returns (uint256) { + if (balance == GPv2Order.BALANCE_INTERNAL) { + IERC20[] memory tks = new IERC20[](1); + tks[0] = tk; + uint256[] memory bals = IBalancerVault(address(vault)).getInternalBalance(user, tks); + return bals[0]; + } else { + return tk.balanceOf(user); + } + } +} diff --git a/test/e2e/BurnFees.t.sol b/test/e2e/BurnFees.t.sol new file mode 100644 index 00000000..e3ac5c1a --- /dev/null +++ b/test/e2e/BurnFees.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; + +import {GPv2Interaction} from "src/contracts/libraries/GPv2Interaction.sol"; +import {GPv2Order} from "src/contracts/libraries/GPv2Order.sol"; +import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol"; + +import {SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol"; +import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol"; +import {Helper, IERC20Mintable} from "./Helper.sol"; + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; + +contract BurnFeesTest is Helper(false) { + IERC20Mintable owl; + IERC20Mintable dai; + + function setUp() public override { + super.setUp(); + + owl = deployMintableErc20("owl", "owl"); + dai = deployMintableErc20("dai", "dai"); + } + + function test_uses_post_interaction_to_burn_settlement_fees() external { + Vm.Wallet memory trader1 = vm.createWallet("trader1"); + Vm.Wallet memory trader2 = vm.createWallet("trader2"); + + // mint some owl to trader1 + owl.mint(trader1.addr, 1001 ether); + vm.prank(trader1.addr); + // approve owl for trading on settlement contract + owl.approve(vaultRelayer, type(uint256).max); + // place order to sell 1000 owl for min 1000 dai + encoder.signEncodeTrade( + vm, + trader1, + GPv2Order.Data({ + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellToken: owl, + buyToken: dai, + sellAmount: 1000 ether, + buyAmount: 1000 ether, + feeAmount: 1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // mint some dai to trader2 + dai.mint(trader2.addr, 1000 ether); + vm.prank(trader2.addr); + // approve dai for trading on settlement contract + dai.approve(vaultRelayer, type(uint256).max); + // place order to BUY 1000 owl with max 1000 dai + encoder.signEncodeTrade( + vm, + trader2, + GPv2Order.Data({ + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellToken: dai, + buyToken: owl, + sellAmount: 1000 ether, + buyAmount: 1000 ether, + feeAmount: 0, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // add post interaction to burn owl fees + encoder.addInteraction( + GPv2Interaction.Data({target: address(owl), value: 0, callData: abi.encodeCall(owl.burn, (1 ether))}), + SettlementEncoder.InteractionStage.POST + ); + + // set the token prices + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = owl; + tokens[1] = dai; + uint256[] memory prices = new uint256[](2); + prices[0] = 1; + prices[1] = 1; + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + vm.prank(solver); + vm.expectEmit(); + emit IERC20.Transfer(address(settlement), address(0), 1 ether); + settle(encodedSettlement); + + assertEq(dai.balanceOf(address(settlement)), 0, "dai balance of settlement contract not 0"); + } +} diff --git a/test/e2e/BuyEth.t.sol b/test/e2e/BuyEth.t.sol new file mode 100644 index 00000000..44f5b541 --- /dev/null +++ b/test/e2e/BuyEth.t.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; +import {GPv2Transfer} from "src/contracts/libraries/GPv2Transfer.sol"; + +import { + GPv2Interaction, GPv2Order, GPv2Signing, SettlementEncoder +} from "test/libraries/encoders/SettlementEncoder.sol"; +import {Registry, TokenRegistry} from "test/libraries/encoders/TokenRegistry.sol"; + +import {Helper} from "./Helper.sol"; + +interface IUSDT { + function getOwner() external view returns (address); + function issue(uint256) external; + // approve and transfer doesn't return the bool for USDT + function approve(address, uint256) external; + function transfer(address, uint256) external; +} + +IUSDT constant USDT = IUSDT(0xdAC17F958D2ee523a2206206994597C13D831ec7); +IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; + +contract BuyEthTest is Helper(true) { + // Settle a trivial batch between two overlapping trades: + // + // /----(1. SELL 1 WETH for USDT if p(WETH) >= 1100)----\ + // | v + // [USDT] [(W)ETH] + // ^ | + // \-----(2. BUY 1 ETH for USDT if p(WETH) <= 1200)-----/ + function test_should_unwrap_weth_for_eth_buy_orders() external { + Vm.Wallet memory trader1 = vm.createWallet("trader1"); + Vm.Wallet memory trader2 = vm.createWallet("trader2"); + + // give some weth to trader1 + deal(address(WETH), trader1.addr, 1.001 ether); + // approve weth for trading on the vault + vm.prank(trader1.addr); + WETH.approve(vaultRelayer, type(uint256).max); + // place the weth to usdt swap order + encoder.signEncodeTrade( + vm, + trader1, + GPv2Order.Data({ + sellToken: WETH, + buyToken: IERC20(address(USDT)), + receiver: trader1.addr, + sellAmount: 1 ether, + buyAmount: 1100e6, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 0.001 ether, + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // give some usdt to trader2 + _mintUsdt(trader2.addr, 1201.2e6); + // approve usdt for trading on the vault + vm.prank(trader2.addr); + USDT.approve(vaultRelayer, type(uint256).max); + // place the usdt to eth swap order + encoder.signEncodeTrade( + vm, + trader2, + GPv2Order.Data({ + sellToken: IERC20(address(USDT)), + buyToken: IERC20(GPv2Transfer.BUY_ETH_ADDRESS), + receiver: trader2.addr, + sellAmount: 1200e6, + buyAmount: 1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 1.2e6, + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // encode the weth withdraw interaction + encoder.addInteraction( + GPv2Interaction.Data({ + target: address(WETH), + value: 0, + callData: abi.encodeWithSignature("withdraw(uint256)", 1 ether) + }), + SettlementEncoder.InteractionStage.INTRA + ); + + // set the token prices + IERC20[] memory tokens = new IERC20[](3); + tokens[0] = WETH; + tokens[1] = IERC20(GPv2Transfer.BUY_ETH_ADDRESS); + tokens[2] = IERC20(address(USDT)); + uint256[] memory prices = new uint256[](3); + prices[0] = 1150e6; + prices[1] = 1150e6; + prices[2] = 1 ether; + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + + uint256 trader2InitialBalance = trader2.addr.balance; + vm.prank(solver); + settle(encodedSettlement); + assertEq( + WETH.balanceOf(address(settlement)), + 0.001 ether, + "settlement contract's weth balance from trade fee not as expected" + ); // the fee + assertEq(WETH.balanceOf(trader1.addr), 0, "trader1 weth balance is not 0"); + assertEq( + trader2.addr.balance, trader2InitialBalance + 1 ether, "trader2 eth balance did not increase as expected" + ); + } + + function _mintUsdt(address receiver, uint256 amt) internal { + address owner = USDT.getOwner(); + vm.startPrank(owner); + USDT.issue(amt); + USDT.transfer(receiver, amt); + vm.stopPrank(); + } +} diff --git a/test/e2e/ContractOrdersWithGnosisSafe.t.sol b/test/e2e/ContractOrdersWithGnosisSafe.t.sol new file mode 100644 index 00000000..17a09184 --- /dev/null +++ b/test/e2e/ContractOrdersWithGnosisSafe.t.sol @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; + +import {GPv2Order} from "src/contracts/libraries/GPv2Order.sol"; +import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol"; + +import {Eip712} from "../libraries/Eip712.sol"; + +import {Sign} from "../libraries/Sign.sol"; +import {SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol"; +import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol"; +import {Helper, IERC20Mintable} from "./Helper.sol"; + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; + +interface ISafeProxyFactory { + function createProxy(address singleton, bytes calldata data) external returns (ISafe); +} + +interface ISafe { + enum Operation { + Call, + DelegateCall + } + + function setup( + address[] calldata _owners, + uint256 _threshold, + address to, + bytes calldata data, + address fallbackHandler, + address paymentToken, + uint256 payment, + address payable paymentReceiver + ) external; + + function execTransaction( + address to, + uint256 value, + bytes calldata data, + Operation operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address payable refundReceiver, + bytes memory signatures + ) external payable returns (bool success); + + function getTransactionHash( + address to, + uint256 value, + bytes calldata data, + Operation operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address refundReceiver, + uint256 _nonce + ) external view returns (bytes32); + + function nonce() external view returns (uint256); + + function getMessageHash(bytes calldata) external view returns (bytes32); + + function isValidSignature(bytes32, bytes calldata) external view returns (bytes4); +} + +ISafeProxyFactory constant SAFE_PROXY_FACTORY = ISafeProxyFactory(0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2); +address constant SAFE_SINGLETON = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; +address constant SAFE_COMPATIBILITY_FALLBACK_HANDLER = 0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4; +bytes4 constant EIP1271_MAGICVALUE = bytes4(keccak256("isValidSignature(bytes32,bytes)")); + +contract ContractOrdersWithGnosisSafeTest is Helper(true) { + IERC20Mintable dai; + IERC20Mintable wETH; + + ISafe safe; + + Vm.Wallet signer1; + Vm.Wallet signer2; + Vm.Wallet signer3; + Vm.Wallet signer4; + Vm.Wallet signer5; + + function setUp() public override { + super.setUp(); + + dai = deployMintableErc20("dai", "dai"); + wETH = deployMintableErc20("wETH", "wETH"); + + signer1 = vm.createWallet("signer1"); + signer2 = vm.createWallet("signer2"); + signer3 = vm.createWallet("signer3"); + signer4 = vm.createWallet("signer4"); + signer5 = vm.createWallet("signer5"); + + address[] memory signers = new address[](5); + signers[0] = signer1.addr; + signers[1] = signer2.addr; + signers[2] = signer3.addr; + signers[3] = signer4.addr; + signers[4] = signer5.addr; + + bytes memory data = abi.encodeCall( + ISafe.setup, + (signers, 2, address(0), hex"", SAFE_COMPATIBILITY_FALLBACK_HANDLER, address(0), 0, payable(address(0))) + ); + safe = SAFE_PROXY_FACTORY.createProxy(SAFE_SINGLETON, data); + } + + function test_should_settle_matching_orders() external { + // EOA trader: sell 1 wETH for 900 dai + // Safe: buy 1 wETH for 1100 dai + // Settlement price at 1000 dai for 1 wETH. + + // mint some tokens to trader + wETH.mint(trader.addr, 1.001 ether); + // approve the tokens for trading on settlement contract + vm.prank(trader.addr); + wETH.approve(vaultRelayer, type(uint256).max); + + // place order to sell 1 wETH for min 900 dai + encoder.signEncodeTrade( + vm, + trader, + GPv2Order.Data({ + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellToken: wETH, + buyToken: dai, + sellAmount: 1 ether, + buyAmount: 900 ether, + feeAmount: 0.001 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // mint some dai to the safe + dai.mint(address(safe), 1110 ether); + // approve dai for trading on settlement contract + _execSafeTransaction( + safe, address(dai), 0, abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max)), signer1, signer2 + ); + assertEq(dai.allowance(address(safe), vaultRelayer), type(uint256).max, "allowance not as expected"); + + // place order to buy 1 wETH with max 1100 dai + GPv2Order.Data memory order = GPv2Order.Data({ + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellToken: dai, + buyToken: wETH, + sellAmount: 1100 ether, + buyAmount: 1 ether, + feeAmount: 10 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }); + + bytes32 orderHash = Eip712.typedDataHash(Eip712.toEip712SignedStruct(order), domainSeparator); + bytes32 safeMessageHash = safe.getMessageHash(abi.encode(orderHash)); + bytes memory signatures = _safeSignature(signer3, signer4, safeMessageHash); + + assertEq(safe.isValidSignature(orderHash, signatures), EIP1271_MAGICVALUE, "invalid signature for the order"); + + encoder.encodeTrade( + order, + Sign.Signature({scheme: GPv2Signing.Scheme.Eip1271, data: abi.encodePacked(address(safe), signatures)}), + 0 + ); + + // set token prices + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = dai; + tokens[1] = wETH; + uint256[] memory prices = new uint256[](2); + prices[0] = 1 ether; + prices[1] = 1000 ether; + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + + vm.prank(solver); + settle(encodedSettlement); + + assertEq(wETH.balanceOf(trader.addr), 0, "trader weth balance not as expected"); + assertEq(dai.balanceOf(trader.addr), 1000 ether, "trader dai balance not as expected"); + + assertEq(wETH.balanceOf(address(safe)), 1 ether, "safe weth balance not as expected"); + assertEq(dai.balanceOf(address(safe)), 100 ether, "safe dai balance not as expected"); + + assertEq(wETH.balanceOf(address(settlement)), 0.001 ether, "settlement weth fee not as expected"); + assertEq(dai.balanceOf(address(settlement)), 10 ether, "settlement dai fee not as expected"); + } + + function _execSafeTransaction( + ISafe safe_, + address to, + uint256 value, + bytes memory data, + Vm.Wallet memory signer1_, + Vm.Wallet memory signer2_ + ) internal { + uint256 nonce = safe_.nonce(); + bytes32 hash = + safe_.getTransactionHash(to, value, data, ISafe.Operation.Call, 0, 0, 0, address(0), address(0), nonce); + bytes memory signatures = _safeSignature(signer1_, signer2_, hash); + safe_.execTransaction( + to, value, data, ISafe.Operation.Call, 0, 0, 0, address(0), payable(address(0)), signatures + ); + } + + function _sign(Vm.Wallet memory wallet, bytes32 hash) internal returns (bytes memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet, hash); + return abi.encodePacked(r, s, v); + } + + function _safeSignature(Vm.Wallet memory signer1_, Vm.Wallet memory signer2_, bytes32 hash) + internal + returns (bytes memory) + { + bytes memory signature1 = _sign(signer1_, hash); + bytes memory signature2 = _sign(signer2_, hash); + bytes memory signatures = signer1_.addr < signer2_.addr + ? abi.encodePacked(signature1, signature2) + : abi.encodePacked(signature2, signature1); + return signatures; + } +} diff --git a/test/e2e/Deployment.t.sol b/test/e2e/Deployment.t.sol new file mode 100644 index 00000000..456549e5 --- /dev/null +++ b/test/e2e/Deployment.t.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {GPv2AllowListAuthentication} from "src/contracts/GPv2AllowListAuthentication.sol"; + +import {Helper} from "./Helper.sol"; + +interface IEIP173Proxy { + function owner() external view returns (address); +} + +// ref: https://github.com/wighawag/hardhat-deploy/blob/e0ffcf9e7dc92b246e832c4d175f9dbd8b6df14d/solc_0.8/proxy/EIP173Proxy.sol +bytes32 constant EIP173_IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + +contract DeploymentTest is Helper(false) { + event Metadata(string, bytes); + + function test__same_built_and_deployed_bytecode_metadata__authenticator() external { + _assertBuiltAndDeployedMetadataCoincide(address(allowListImpl), "GPv2AllowListAuthentication"); + } + + function test__same_built_and_deployed_bytecode_metadata__settlement() external { + _assertBuiltAndDeployedMetadataCoincide(address(settlement), "GPv2Settlement"); + } + + function test__same_built_and_deployed_bytecode_metadata__vault_relayer() external { + _assertBuiltAndDeployedMetadataCoincide(address(vaultRelayer), "GPv2VaultRelayer"); + } + + function test__determininstic_addresses__authenticator__proxy() external view { + assertEq( + _computeCreate2Addr( + abi.encodePacked( + vm.getCode("EIP173Proxy"), + abi.encode( + _implementationAddress(address(allowList)), + owner, + abi.encodeCall(GPv2AllowListAuthentication.initializeManager, (owner)) + ) + ) + ), + address(authenticator), + "authenticator address not as expected" + ); + } + + function test__determininstic_addresses__authenticator__implementation() external view { + assertEq( + _computeCreate2Addr(vm.getCode("GPv2AllowListAuthentication")), + _implementationAddress(address(allowList)), + "authenticator impl address not as expected" + ); + } + + function test__determininstic_addresses__settlement() external view { + assertEq( + _computeCreate2Addr( + abi.encodePacked(vm.getCode("GPv2Settlement"), abi.encode(address(authenticator), address(vault))) + ), + address(settlement), + "settlement address not as expected" + ); + } + + function test__authorization__authenticator_has_dedicated_owner() external view { + assertEq(IEIP173Proxy(address(allowList)).owner(), owner, "owner not as expected"); + } + + function test__authorization__authenticator_has_dedicated_manager() external view { + assertEq(allowList.manager(), owner, "manager not as expected"); + } + + function _assertBuiltAndDeployedMetadataCoincide(address addr, string memory artifactName) internal { + bytes memory deployedCode = vm.getDeployedCode(artifactName); + assertEq( + keccak256(_getMetadata(string(abi.encodePacked("deployed ", artifactName)), addr.code)), + keccak256(_getMetadata(artifactName, deployedCode)), + "metadata doesnt match" + ); + } + + function _getMetadata(string memory hint, bytes memory bytecode) internal returns (bytes memory metadata) { + assembly ("memory-safe") { + // the last two bytes encode the size of the cbor encoded metadata + let bytecodeSize := mload(bytecode) + let bytecodeStart := add(bytecode, 0x20) + let cborSizeOffset := add(bytecodeStart, sub(bytecodeSize, 0x20)) + let cborSize := and(mload(cborSizeOffset), 0xffff) + + // copy the metadata out + metadata := mload(0x40) + let metadataSize := add(cborSize, 0x02) + mstore(metadata, metadataSize) + let metadataOffset := add(bytecodeStart, sub(bytecodeSize, metadataSize)) + mcopy(add(metadata, 0x20), metadataOffset, metadataSize) + + // update free memory ptr + mstore(0x40, add(metadata, add(metadataSize, 0x20))) + } + emit Metadata(hint, metadata); + } + + function _computeCreate2Addr(bytes memory initCode) internal view returns (address) { + return vm.computeCreate2Address(SALT, hashInitCode(initCode), deployer); + } + + function _implementationAddress(address proxy) internal view returns (address) { + return address(uint160(uint256(vm.load(proxy, EIP173_IMPLEMENTATION_SLOT)))); + } +} diff --git a/test/e2e/ERC20Mintable.sol b/test/e2e/ERC20Mintable.sol new file mode 100644 index 00000000..30b53884 --- /dev/null +++ b/test/e2e/ERC20Mintable.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// solhint-disable-next-line compiler-version +pragma solidity ^0.7; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ERC20Mintable is ERC20 { + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(uint256 amount) external { + _burn(msg.sender, amount); + } +} diff --git a/test/e2e/Helper.sol b/test/e2e/Helper.sol new file mode 100644 index 00000000..c56e20cb --- /dev/null +++ b/test/e2e/Helper.sol @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Test, Vm, stdJson} from "forge-std/Test.sol"; +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; + +import {GPv2AllowListAuthentication} from "src/contracts/GPv2AllowListAuthentication.sol"; +import { + GPv2Authentication, + GPv2Interaction, + GPv2Settlement, + GPv2Trade, + IERC20, + IVault +} from "src/contracts/GPv2Settlement.sol"; + +import {WETH9} from "./WETH9.sol"; +import {SettlementEncoder} from "test/libraries/encoders/SettlementEncoder.sol"; +import {SwapEncoder} from "test/libraries/encoders/SwapEncoder.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; +address constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; + +uint256 constant FORK_BLOCK_NUMBER = 20913563; + +interface IAuthorizer { + function grantRole(bytes32, address) external; + function canPerform(bytes32 actionId, address account, address where) external view returns (bool); +} + +interface IERC20Mintable is IERC20 { + function mint(address, uint256) external; + function burn(uint256) external; +} + +interface IBalancerVault is IVault { + function getAuthorizer() external view returns (address); +} + +// solhint-disable func-name-mixedcase +// solhint-disable max-states-count +abstract contract Helper is Test { + using stdJson for string; + using SettlementEncoder for SettlementEncoder.State; + + address internal deployer; + address internal owner; + GPv2Settlement internal settlement; + bytes32 internal domainSeparator; + GPv2Authentication internal authenticator; + IVault internal vault; + GPv2AllowListAuthentication internal allowList; + GPv2AllowListAuthentication internal allowListImpl; + address vaultRelayer; + address balancerVaultAuthorizer; + + SettlementEncoder.State internal encoder; + SwapEncoder.State internal swapEncoder; + + address internal solver; + Vm.Wallet internal trader; + + bool immutable isForked; + uint256 forkId; + + WETH9 weth; + + bytes32 constant SALT = "Mattresses in Berlin!"; + + constructor(bool _isForked) { + isForked = _isForked; + } + + function setUp() public virtual { + if (isForked) { + string memory forkUrl; + + try vm.envString("FORK_URL") returns (string memory url) { + forkUrl = url; + } catch { + forkUrl = "https://eth.merkle.io"; + } + + forkId = vm.createSelectFork(forkUrl, FORK_BLOCK_NUMBER); + weth = WETH9(payable(WETH)); + } else { + weth = new WETH9(); + } + + // Configure addresses + deployer = makeAddr("E2E.Helper: deployer"); + owner = makeAddr("E2E.Helper: owner"); + solver = makeAddr("E2E.Helper: solver"); + vm.startPrank(deployer); + + // Deploy the allowlist manager + allowListImpl = new GPv2AllowListAuthentication{salt: SALT}(); + allowList = GPv2AllowListAuthentication( + deployProxy(address(allowListImpl), owner, abi.encodeCall(allowListImpl.initializeManager, (owner)), SALT) + ); + authenticator = allowList; + + (balancerVaultAuthorizer, vault) = _deployBalancerVault(); + + // Deploy the settlement contract + settlement = new GPv2Settlement{salt: SALT}(authenticator, vault); + vaultRelayer = address(settlement.vaultRelayer()); + + // Reset the prank + vm.stopPrank(); + + _grantBalancerRolesToRelayer(balancerVaultAuthorizer, address(vault), vaultRelayer); + + // By default, allow `solver` to settle + vm.prank(owner); + allowList.addSolver(solver); + + // Configure default encoders + encoder = SettlementEncoder.makeSettlementEncoder(); + swapEncoder = SwapEncoder.makeSwapEncoder(); + + // Set the domain separator + domainSeparator = settlement.domainSeparator(); + + // Create wallets + trader = vm.createWallet("E2E.Helper: trader"); + } + + function settle(SettlementEncoder.EncodedSettlement memory _settlement) internal { + settlement.settle(_settlement.tokens, _settlement.clearingPrices, _settlement.trades, _settlement.interactions); + } + + function swap(SwapEncoder.EncodedSwap memory _swap) internal { + settlement.swap(_swap.swaps, _swap.tokens, _swap.trade); + } + + function emptySettlement() internal pure returns (SettlementEncoder.EncodedSettlement memory) { + return SettlementEncoder.EncodedSettlement({ + tokens: new IERC20[](0), + clearingPrices: new uint256[](0), + trades: new GPv2Trade.Data[](0), + interactions: [new GPv2Interaction.Data[](0), new GPv2Interaction.Data[](0), new GPv2Interaction.Data[](0)] + }); + } + + function _deployBalancerVault() internal returns (address, IBalancerVault) { + if (isForked) { + IBalancerVault balancerVault = IBalancerVault(BALANCER_VAULT); + address authorizer = balancerVault.getAuthorizer(); + return (authorizer, balancerVault); + } else { + bytes memory authorizerInitCode = abi.encodePacked(_getBalancerBytecode("Authorizer"), abi.encode(owner)); + address authorizer = _create(authorizerInitCode, 0); + + bytes memory vaultInitCode = + abi.encodePacked(_getBalancerBytecode("Vault"), abi.encode(authorizer, address(weth), 0, 0)); + address deployedVault = _create(vaultInitCode, 0); + + return (authorizer, IBalancerVault(deployedVault)); + } + } + + function _grantBalancerRolesToRelayer(address authorizer, address deployedVault, address relayer) internal { + _grantBalancerActionRole( + authorizer, deployedVault, relayer, "manageUserBalance((uint8,address,uint256,address,address)[])" + ); + _grantBalancerActionRole( + authorizer, + deployedVault, + relayer, + "batchSwap(uint8,(bytes32,uint256,uint256,uint256,bytes)[],address[],(address,bool,address,bool),int256[],uint256)" + ); + } + + function _grantBalancerActionRole(address authorizer, address balVault, address to, string memory action) + internal + { + bytes32 actionId = _getActionId(action, balVault); + vm.mockCall( + address(authorizer), abi.encodeCall(IAuthorizer.canPerform, (actionId, to, balVault)), abi.encode(true) + ); + } + + function _getActionId(string memory fnDef, address vaultAddr) internal pure returns (bytes32) { + bytes32 hash = keccak256(bytes(fnDef)); + bytes4 selector = bytes4(hash); + return keccak256(abi.encodePacked(uint256(uint160(vaultAddr)), selector)); + } + + function _getBalancerBytecode(string memory artifactName) internal view returns (bytes memory) { + string memory data = vm.readFile(string(abi.encodePacked("balancer/", artifactName, ".json"))); + return vm.parseJsonBytes(data, ".bytecode"); + } + + function _create(bytes memory initCode, uint256 value) internal returns (address deployed) { + assembly ("memory-safe") { + deployed := create(value, add(initCode, 0x20), mload(initCode)) + } + require(deployed != address(0), "deployment failed"); + } + + function _create2(bytes memory initCode, uint256 value, bytes32 salt) internal returns (address deployed) { + assembly ("memory-safe") { + deployed := create2(value, add(initCode, 0x20), mload(initCode), salt) + } + require(deployed != address(0), "deployment failed"); + } + + function deployMintableErc20(string memory name, string memory symbol) internal returns (IERC20Mintable token) { + // need to use like this because OZ requires ^0.7 and tests are on ^0.8 + bytes memory initCode = abi.encodePacked(vm.getCode("ERC20Mintable"), abi.encode(name, symbol)); + token = IERC20Mintable(_create(initCode, 0)); + } + + function deployProxy(address implAddress, address ownerAddress, bytes memory data, bytes32 salt) + internal + returns (address proxy) + { + proxy = + _create2(abi.encodePacked(vm.getCode("EIP173Proxy"), abi.encode(implAddress, ownerAddress, data)), 0, salt); + } +} diff --git a/test/e2e/InternalBalances.t.sol b/test/e2e/InternalBalances.t.sol new file mode 100644 index 00000000..77461e26 --- /dev/null +++ b/test/e2e/InternalBalances.t.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; +import {IVault} from "src/contracts/interfaces/IVault.sol"; + +import {GPv2Order, GPv2Signing, SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol"; +import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol"; +import {Helper, IERC20Mintable} from "./Helper.sol"; + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; + +interface IBalancerVault is IVault { + function setRelayerApproval(address, address, bool) external; + function getInternalBalance(address user, IERC20[] memory tokens) external view returns (uint256[] memory); + function hasApprovedRelayer(address, address) external view returns (bool); +} + +contract InternalBalancesTest is Helper(false) { + IERC20Mintable token1; + IERC20Mintable token2; + + function setUp() public override { + super.setUp(); + + token1 = deployMintableErc20("TK1", "TK1"); + token2 = deployMintableErc20("TK2", "TK2"); + + vm.startPrank(address(settlement)); + token1.approve(address(vault), type(uint256).max); + token2.approve(address(vault), type(uint256).max); + vm.stopPrank(); + } + + function test_should_settle_orders_buying_and_selling_with_internal_balances() external { + Vm.Wallet memory trader1 = vm.createWallet("trader1"); + Vm.Wallet memory trader2 = vm.createWallet("trader2"); + Vm.Wallet memory trader3 = vm.createWallet("trader3"); + Vm.Wallet memory trader4 = vm.createWallet("trader4"); + + // mint some tokens to trader1 + _mintTokens(token1, trader1.addr, 1.001 ether); + + // approve tokens to the balancer vault and approve the settlement contract to + // be able to spend the balancer internal/external balances + vm.startPrank(trader1.addr); + token1.approve(address(vault), type(uint256).max); + IBalancerVault(address(vault)).setRelayerApproval(trader1.addr, vaultRelayer, true); + vm.stopPrank(); + + // place order for selling 1 token1 for 500 token2 + encoder.signEncodeTrade( + vm, + trader1, + GPv2Order.Data({ + sellToken: token1, + buyToken: token2, + receiver: trader1.addr, + sellAmount: 1 ether, + buyAmount: 500 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 0.001 ether, + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_EXTERNAL, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // mint some tokens to trader2 + _mintTokens(token2, trader2.addr, 300.3 ether); + + // approve tokens to the balancer vault and deposit some tokens to balancer internal + // balance + vm.startPrank(trader2.addr); + token2.approve(address(vault), type(uint256).max); + IVault.UserBalanceOp[] memory ops = new IVault.UserBalanceOp[](1); + ops[0] = IVault.UserBalanceOp({ + kind: IVault.UserBalanceOpKind.DEPOSIT_INTERNAL, + asset: token2, + amount: 300.3 ether, + sender: trader2.addr, + recipient: payable(trader2.addr) + }); + vault.manageUserBalance(ops); + IBalancerVault(address(vault)).setRelayerApproval(trader2.addr, vaultRelayer, true); + vm.stopPrank(); + + // place order for buying 0.5 token1 with max 300 token2 + encoder.signEncodeTrade( + vm, + trader2, + GPv2Order.Data({ + sellToken: token2, + buyToken: token1, + receiver: trader2.addr, + sellAmount: 300 ether, + buyAmount: 0.5 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 0.3 ether, + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_INTERNAL, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // mint some tokens to trader3 + _mintTokens(token1, trader3.addr, 2.002 ether); + + // approve the tokens to cow vault relayer + vm.prank(trader3.addr); + token1.approve(vaultRelayer, type(uint256).max); + + // place order for selling 2 token1 for min 1000 token2 + encoder.signEncodeTrade( + vm, + trader3, + GPv2Order.Data({ + sellToken: token1, + buyToken: token2, + receiver: trader3.addr, + sellAmount: 2 ether, + buyAmount: 1000 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 0.002 ether, + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_INTERNAL + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // mint some tokens to trader4 + _mintTokens(token2, trader4.addr, 1501.5 ether); + + // approve tokens to the balancer vault and deposit some tokens to balancer internal + // balance + vm.startPrank(trader4.addr); + token2.approve(address(vault), type(uint256).max); + ops = new IVault.UserBalanceOp[](1); + ops[0] = IVault.UserBalanceOp({ + kind: IVault.UserBalanceOpKind.DEPOSIT_INTERNAL, + asset: token2, + amount: 1501.5 ether, + sender: trader4.addr, + recipient: payable(trader4.addr) + }); + IBalancerVault(address(vault)).manageUserBalance(ops); + IBalancerVault(address(vault)).setRelayerApproval(trader4.addr, vaultRelayer, true); + vm.stopPrank(); + + // place order to buy 2.5 token1 with max 1500 token2 + encoder.signEncodeTrade( + vm, + trader4, + GPv2Order.Data({ + sellToken: token2, + buyToken: token1, + receiver: trader4.addr, + sellAmount: 1500 ether, + buyAmount: 2.5 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 1.5 ether, + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_INTERNAL, + buyTokenBalance: GPv2Order.BALANCE_INTERNAL + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // set token prices + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = token1; + tokens[1] = token2; + uint256[] memory prices = new uint256[](2); + prices[0] = 550; + prices[1] = 1; + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + + // settle the orders + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + vm.prank(solver); + settle(encodedSettlement); + + assertEq(token2.balanceOf(trader1.addr), 550 ether, "trader1 amountOut not as expected"); + assertEq(token1.balanceOf(trader2.addr), 0.5 ether, "trader2 amountOut not as expected"); + assertEq(_getInternalBalance(address(token2), trader3.addr), 1100 ether, "trader3 amountOut not as expected"); + assertEq(_getInternalBalance(address(token1), trader4.addr), 2.5 ether, "trader4 amountOut not as expected"); + + assertEq(token1.balanceOf(address(settlement)), 0.003 ether, "token1 settlement fee amount not as expected"); + assertEq(token2.balanceOf(address(settlement)), 1.8 ether, "token2 settlement fee amount not as expected"); + } + + function _mintTokens(IERC20Mintable token, address to, uint256 amt) internal { + token.mint(to, amt); + } + + function _getInternalBalance(address token, address who) internal view returns (uint256) { + IERC20[] memory tokens = new IERC20[](1); + tokens[0] = IERC20(token); + uint256[] memory bals = IBalancerVault(address(vault)).getInternalBalance(who, tokens); + return bals[0]; + } +} diff --git a/test/e2e/NonStandardErc20.t.sol b/test/e2e/NonStandardErc20.t.sol new file mode 100644 index 00000000..1445d1d9 --- /dev/null +++ b/test/e2e/NonStandardErc20.t.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {Helper} from "./Helper.sol"; +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; + +import {GPv2Order, GPv2Signing, SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol"; +import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol"; + +abstract contract NonStandardERC20 { + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + function mint(address to, uint256 amount) external { + balanceOf[to] += amount; + } + + function approve(address spender, uint256 amount) external { + allowance[msg.sender][spender] = amount; + } + + function transfer_(address to, uint256 amount) internal { + balanceOf[msg.sender] -= amount; + balanceOf[to] += amount; + } + + function transferFrom_(address from, address to, uint256 amount) internal { + allowance[from][msg.sender] -= amount; + balanceOf[from] -= amount; + balanceOf[to] += amount; + } +} + +contract ERC20NoReturn is NonStandardERC20 { + function transfer(address to, uint256 amount) external { + transfer_(to, amount); + } + + function transferFrom(address from, address to, uint256 amount) external { + transferFrom_(from, to, amount); + } +} + +contract ERC20ReturningUint is NonStandardERC20 { + // Largest 256-bit prime :) + uint256 private constant OK = 115792089237316195423570985008687907853269984665640564039457584007913129639747; + + function transfer(address to, uint256 amount) external returns (uint256) { + transfer_(to, amount); + return OK; + } + + function transferFrom(address from, address to, uint256 amount) external returns (uint256) { + transferFrom_(from, to, amount); + return OK; + } +} + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; + +contract NonStandardErc20Test is Helper(false) { + ERC20NoReturn noReturnToken; + ERC20ReturningUint uintReturningToken; + + function setUp() public override { + super.setUp(); + + noReturnToken = new ERC20NoReturn(); + uintReturningToken = new ERC20ReturningUint(); + } + + function test_should_allow_trading_non_standard_erc20_tokens() external { + uint256 amount = 1 ether; + uint256 feeAmount = 0.01 ether; + + Vm.Wallet memory trader1 = vm.createWallet("trader1"); + Vm.Wallet memory trader2 = vm.createWallet("trader2"); + + // mint some noReturnToken tokens to trader1 + noReturnToken.mint(trader1.addr, amount + feeAmount); + vm.prank(trader1.addr); + noReturnToken.approve(vaultRelayer, type(uint256).max); + // place order to swap noReturnToken for uintReturningToken + encoder.signEncodeTrade( + vm, + trader1, + GPv2Order.Data({ + sellToken: IERC20(address(noReturnToken)), + buyToken: IERC20(address(uintReturningToken)), + receiver: trader1.addr, + sellAmount: amount, + buyAmount: amount, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: feeAmount, + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // mint some uintReturningToken tokens to trader2 + uintReturningToken.mint(trader2.addr, amount + feeAmount); + vm.prank(trader2.addr); + uintReturningToken.approve(vaultRelayer, type(uint256).max); + // place order to swap uintReturningToken for noReturnToken + encoder.signEncodeTrade( + vm, + trader2, + GPv2Order.Data({ + sellToken: IERC20(address(uintReturningToken)), + buyToken: IERC20(address(noReturnToken)), + receiver: trader2.addr, + sellAmount: amount, + buyAmount: amount, + validTo: 0xffffffff, + appData: bytes32(uint256(2)), + feeAmount: feeAmount, + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // set token prices + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = IERC20(address(noReturnToken)); + tokens[1] = IERC20(address(uintReturningToken)); + uint256[] memory prices = new uint256[](2); + prices[0] = 1; + prices[1] = 1; + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + + // settle the orders + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + vm.prank(solver); + settle(encodedSettlement); + + assertEq(noReturnToken.balanceOf(address(settlement)), feeAmount, "order1 fee not charged as expected"); + assertEq(noReturnToken.balanceOf(trader2.addr), amount, "order1 swap output not as expected"); + + assertEq(uintReturningToken.balanceOf(address(settlement)), feeAmount, "order2 fee not charged as expected"); + assertEq(uintReturningToken.balanceOf(trader1.addr), amount, "order2 swap output not as expected"); + } +} diff --git a/test/e2e/OffchainAllowances.t.sol b/test/e2e/OffchainAllowances.t.sol new file mode 100644 index 00000000..38d304bf --- /dev/null +++ b/test/e2e/OffchainAllowances.t.sol @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; +import {IVault} from "src/contracts/interfaces/IVault.sol"; +import {GPv2Interaction} from "src/contracts/libraries/GPv2Interaction.sol"; +import {GPv2Order} from "src/contracts/libraries/GPv2Order.sol"; +import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol"; + +import {SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol"; +import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol"; +import {Helper, IERC20Mintable} from "./Helper.sol"; + +interface IERC2612 { + function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external; + function nonces(address owner) external view returns (uint256); + function DOMAIN_SEPARATOR() external view returns (bytes32); +} + +interface IBalancerVault { + function getDomainSeparator() external view returns (bytes32); + function setRelayerApproval(address, address, bool) external; +} + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; + +contract OffchainAllowancesTest is Helper(false) { + IERC20Mintable eur1; + IERC20Mintable eur2; + + Vm.Wallet trader1; + Vm.Wallet trader2; + + function setUp() public override { + super.setUp(); + + eur1 = IERC20Mintable(_create(abi.encodePacked(vm.getCode("ERC20PresetPermit"), abi.encode("eur1")), 0)); + eur2 = IERC20Mintable(_create(abi.encodePacked(vm.getCode("ERC20PresetPermit"), abi.encode("eur1")), 0)); + + trader1 = vm.createWallet("trader1"); + trader2 = vm.createWallet("trader2"); + } + + function test_eip_2612_permits_trader_allowance_with_settlement() external { + // mint and approve tokens to and from trader1 + eur1.mint(trader1.addr, 1 ether); + vm.prank(trader1.addr); + eur1.approve(vaultRelayer, type(uint256).max); + + // place order to sell 1 eur1 for min 1 eur2 from trader1 + encoder.signEncodeTrade( + vm, + trader1, + GPv2Order.Data({ + sellToken: eur1, + buyToken: eur2, + receiver: trader1.addr, + sellAmount: 1 ether, + buyAmount: 1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 0, + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // mint some tokens to trader2 + eur2.mint(trader2.addr, 1 ether); + uint256 nonce = IERC2612(address(eur2)).nonces(trader2.addr); + (uint8 v, bytes32 r, bytes32 s) = _permit(eur2, trader2, vaultRelayer, 1 ether, nonce, 0xffffffff); + // interaction for setting the approval with permit + encoder.addInteraction( + GPv2Interaction.Data({ + target: address(eur2), + value: 0, + callData: abi.encodeCall(IERC2612.permit, (trader2.addr, vaultRelayer, 1 ether, 0xffffffff, v, r, s)) + }), + SettlementEncoder.InteractionStage.PRE + ); + + // buy 1 eur1 with max 1 eur2 + encoder.signEncodeTrade( + vm, + trader2, + GPv2Order.Data({ + sellToken: eur2, + buyToken: eur1, + receiver: trader2.addr, + sellAmount: 1 ether, + buyAmount: 1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 0, + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // set prices + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = eur1; + tokens[1] = eur2; + uint256[] memory prices = new uint256[](2); + prices[0] = 1; + prices[1] = 1; + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + vm.prank(solver); + settle(encodedSettlement); + + assertEq(eur2.balanceOf(trader2.addr), 0, "permit didnt work"); + } + + function test_allows_setting_vault_relayer_approval_with_interactions() external { + // mint and approve tokens to and from trader1 + eur1.mint(trader1.addr, 1 ether); + vm.prank(trader1.addr); + eur1.approve(vaultRelayer, type(uint256).max); + + // place order to sell 1 eur1 for min 1 eur2 from trader1 + encoder.signEncodeTrade( + vm, + trader1, + GPv2Order.Data({ + sellToken: eur1, + buyToken: eur2, + receiver: trader1.addr, + sellAmount: 1 ether, + buyAmount: 1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 0, + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // mint some tokens to trader2 + eur2.mint(trader2.addr, 1 ether); + // deposit tokens into balancer internal balance + vm.startPrank(trader2.addr); + eur2.approve(address(vault), type(uint256).max); + IVault.UserBalanceOp[] memory ops = new IVault.UserBalanceOp[](1); + ops[0] = IVault.UserBalanceOp({ + kind: IVault.UserBalanceOpKind.DEPOSIT_INTERNAL, + asset: eur2, + amount: 1 ether, + sender: trader2.addr, + recipient: payable(trader2.addr) + }); + vault.manageUserBalance(ops); + vm.stopPrank(); + + _grantBalancerActionRole( + balancerVaultAuthorizer, address(vault), address(settlement), "setRelayerApproval(address,address,bool)" + ); + bytes memory approval = abi.encodeCall(IBalancerVault.setRelayerApproval, (trader2.addr, vaultRelayer, true)); + (uint8 v, bytes32 r, bytes32 s) = + _balancerSetRelayerApprovalSignature(trader2, approval, address(settlement), 0, 0xffffffff); + encoder.addInteraction( + GPv2Interaction.Data({ + target: address(vault), + value: 0, + callData: abi.encodePacked(approval, abi.encode(0xffffffff, v, r, s)) + }), + SettlementEncoder.InteractionStage.PRE + ); + + encoder.signEncodeTrade( + vm, + trader2, + GPv2Order.Data({ + sellToken: eur2, + buyToken: eur1, + receiver: trader2.addr, + sellAmount: 1 ether, + buyAmount: 1 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: 0, + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_INTERNAL, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // set prices + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = eur1; + tokens[1] = eur2; + uint256[] memory prices = new uint256[](2); + prices[0] = 1; + prices[1] = 1; + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + + vm.prank(solver); + settle(encodedSettlement); + + assertEq(eur2.balanceOf(trader2.addr), 0, "balancer signed approval didnt work"); + } + + function _permit( + IERC20Mintable token, + Vm.Wallet memory owner, + address spender, + uint256 value, + uint256 nonce, + uint256 deadline + ) internal returns (uint8 v, bytes32 r, bytes32 s) { + bytes32 ds = IERC2612(address(token)).DOMAIN_SEPARATOR(); + bytes32 digest = keccak256( + abi.encodePacked( + hex"1901", + ds, + keccak256( + abi.encode( + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), + owner.addr, + spender, + value, + nonce, + deadline + ) + ) + ) + ); + (v, r, s) = vm.sign(owner, digest); + } + + function _balancerSetRelayerApprovalSignature( + Vm.Wallet memory owner, + bytes memory cd, + address sender, + uint256 nonce, + uint256 deadline + ) internal returns (uint8 v, bytes32 r, bytes32 s) { + bytes32 ds = IBalancerVault(address(vault)).getDomainSeparator(); + bytes memory ecd = abi.encode( + keccak256("SetRelayerApproval(bytes calldata,address sender,uint256 nonce,uint256 deadline)"), + keccak256(cd), + sender, + nonce, + deadline + ); + bytes32 digest = keccak256(abi.encodePacked(hex"1901", ds, keccak256(ecd))); + (v, r, s) = vm.sign(owner, digest); + } +} diff --git a/test/e2e/SmartOrder.t.sol b/test/e2e/SmartOrder.t.sol new file mode 100644 index 00000000..c3dc2dbb --- /dev/null +++ b/test/e2e/SmartOrder.t.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {GPv2Settlement} from "src/contracts/GPv2Settlement.sol"; +import {EIP1271Verifier, GPv2EIP1271} from "src/contracts/interfaces/GPv2EIP1271.sol"; +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; +import {GPv2Order} from "src/contracts/libraries/GPv2Order.sol"; +import {GPv2SafeERC20} from "src/contracts/libraries/GPv2SafeERC20.sol"; +import {SafeMath} from "src/contracts/libraries/SafeMath.sol"; +import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol"; + +import {Sign} from "../libraries/Sign.sol"; +import {SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol"; +import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol"; +import {Helper, IERC20Mintable} from "./Helper.sol"; + +/// @title Proof of Concept Smart Order +/// @author Gnosis Developers +contract SmartSellOrder is EIP1271Verifier { + using GPv2Order for GPv2Order.Data; + using GPv2SafeERC20 for IERC20; + using SafeMath for uint256; + + bytes32 public constant APPDATA = keccak256("SmartSellOrder"); + + address public immutable owner; + bytes32 public immutable domainSeparator; + IERC20 public immutable sellToken; + IERC20 public immutable buyToken; + uint256 public immutable totalSellAmount; + uint256 public immutable totalFeeAmount; + uint32 public immutable validTo; + + constructor( + GPv2Settlement settlement, + IERC20 sellToken_, + IERC20 buyToken_, + uint32 validTo_, + uint256 totalSellAmount_, + uint256 totalFeeAmount_ + ) { + owner = msg.sender; + domainSeparator = settlement.domainSeparator(); + sellToken = sellToken_; + buyToken = buyToken_; + validTo = validTo_; + totalSellAmount = totalSellAmount_; + totalFeeAmount = totalFeeAmount_; + + sellToken_.approve(address(settlement.vaultRelayer()), type(uint256).max); + } + + modifier onlyOwner() { + require(msg.sender == owner, "not owner"); + _; + } + + function withdraw(uint256 amount) external onlyOwner { + sellToken.safeTransfer(owner, amount); + } + + function close() external onlyOwner { + uint256 balance = sellToken.balanceOf(address(this)); + if (balance != 0) { + sellToken.safeTransfer(owner, balance); + } + } + + function isValidSignature(bytes32 hash, bytes memory signature) + external + view + override + returns (bytes4 magicValue) + { + uint256 sellAmount = abi.decode(signature, (uint256)); + GPv2Order.Data memory order = orderForSellAmount(sellAmount); + + if (order.hash(domainSeparator) == hash) { + magicValue = GPv2EIP1271.MAGICVALUE; + } + } + + function orderForSellAmount(uint256 sellAmount) public view returns (GPv2Order.Data memory order) { + order.sellToken = sellToken; + order.buyToken = buyToken; + order.receiver = owner; + order.sellAmount = sellAmount; + order.buyAmount = buyAmountForSellAmount(sellAmount); + order.validTo = validTo; + order.appData = APPDATA; + order.feeAmount = totalFeeAmount.mul(sellAmount).div(totalSellAmount); + order.kind = GPv2Order.KIND_SELL; + // NOTE: We counter-intuitively set `partiallyFillable` to `false`, even + // if the smart order as a whole acts like a partially fillable order. + // This is done since, once a settlement commits to a specific sell + // amount, then it is expected to use it completely and not partially. + order.partiallyFillable = false; + order.sellTokenBalance = GPv2Order.BALANCE_ERC20; + order.buyTokenBalance = GPv2Order.BALANCE_ERC20; + } + + function buyAmountForSellAmount(uint256 sellAmount) private view returns (uint256 buyAmount) { + uint256 feeAdjustedBalance = + sellToken.balanceOf(address(this)).mul(totalSellAmount).div(totalSellAmount.add(totalFeeAmount)); + uint256 soldAmount = totalSellAmount > feeAdjustedBalance ? totalSellAmount - feeAdjustedBalance : 0; + + // NOTE: This is currently a silly price strategy where the xrate + // increases linearly from 1:1 to 1:2 as the smart order gets filled. + // This can be extended to more complex "price curves". + buyAmount = sellAmount.mul(totalSellAmount.add(sellAmount).add(soldAmount)).div(totalSellAmount); + } +} + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; + +contract SmartOrderTest is Helper(false) { + IERC20Mintable token1; + IERC20Mintable token2; + + function setUp() public override { + super.setUp(); + + token1 = deployMintableErc20("TK1", "TK1"); + token2 = deployMintableErc20("TK2", "TK2"); + } + + function test_permits_trader_allowance_with_settlement() external { + Vm.Wallet memory trader1 = vm.createWallet("trader1"); + Vm.Wallet memory trader2 = vm.createWallet("trader2"); + + // mint some tokens + token1.mint(trader1.addr, 1.01 ether); + vm.prank(trader1.addr); + // approve tokens to vault relayer + token1.approve(vaultRelayer, type(uint256).max); + // place order to buy 0.5 token2 with 1 token1 max + encoder.signEncodeTrade( + vm, + trader1, + GPv2Order.Data({ + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellToken: token1, + buyToken: token2, + sellAmount: 1 ether, + buyAmount: 0.5 ether, + feeAmount: 0.01 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + vm.prank(trader2.addr); + SmartSellOrder smartOrder = new SmartSellOrder(settlement, token2, token1, 0xffffffff, 1 ether, 0.1 ether); + token2.mint(trader2.addr, 1.1 ether); + vm.prank(trader2.addr); + token2.transfer(address(smartOrder), 1.1 ether); + + uint256 smartOrderSellAmount = 0.5 ether; + GPv2Order.Data memory smartOrderTrade = smartOrder.orderForSellAmount(smartOrderSellAmount); + GPv2Order.Data memory expectedOrder = GPv2Order.Data({ + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellToken: token2, + buyToken: token1, + receiver: trader2.addr, + sellAmount: smartOrderSellAmount, + buyAmount: 0.75 ether, + feeAmount: 0.05 ether, + validTo: 0xffffffff, + appData: smartOrder.APPDATA(), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }); + assertEq( + keccak256(abi.encode(smartOrderTrade)), keccak256(abi.encode(expectedOrder)), "smart order not as expected" + ); + + encoder.encodeTrade( + smartOrderTrade, + Sign.Signature({ + scheme: GPv2Signing.Scheme.Eip1271, + data: abi.encodePacked(address(smartOrder), abi.encode(smartOrderSellAmount)) + }), + 0 + ); + + // set token prices + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = token1; + tokens[1] = token2; + uint256[] memory prices = new uint256[](2); + prices[0] = 10; + prices[1] = 15; + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + + vm.prank(solver); + settle(encodedSettlement); + + assertEq(token1.balanceOf(trader2.addr), 0.75 ether); + } +} diff --git a/test/e2e/UniswapTrade.t.sol b/test/e2e/UniswapTrade.t.sol new file mode 100644 index 00000000..ab71c6f2 --- /dev/null +++ b/test/e2e/UniswapTrade.t.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; + +import {GPv2Interaction} from "src/contracts/libraries/GPv2Interaction.sol"; +import {GPv2Order} from "src/contracts/libraries/GPv2Order.sol"; +import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol"; + +import {SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol"; +import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol"; +import {Helper, IERC20Mintable} from "./Helper.sol"; + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; + +interface IUniswapV2Factory { + function createPair(address, address) external returns (address); +} + +interface IUniswapV2Pair { + function token0() external view returns (address); + function mint(address) external; + function swap(uint256, uint256, address, bytes calldata) external; + function getReserves() external view returns (uint256, uint256); +} + +contract UniswapTradeTest is Helper(true) { + IERC20Mintable dai; + IERC20Mintable wETH; + + IUniswapV2Factory factory; + IUniswapV2Pair uniswapPair; + + bool isWethToken0; + + function setUp() public override { + super.setUp(); + + dai = deployMintableErc20("dai", "dai"); + wETH = deployMintableErc20("wETH", "wETH"); + + factory = IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); + uniswapPair = IUniswapV2Pair(factory.createPair(address(wETH), address(dai))); + + isWethToken0 = uniswapPair.token0() == address(wETH); + } + + // Settles the following batch: + // + // /----(1. SELL 1 wETH for dai if p(wETH) >= 500)-----\ + // | | + // | v + // [dai]<---(Uniswap Pair 1000 wETH / 600.000 dai)--->[wETH] + // ^ | + // | | + // \----(2. BUY 0.5 wETH for dai if p(wETH) <= 600)----/ + function test_should_two_overlapping_orders_and_trade_surplus_with_uniswap() external { + uint256 wethReserve = 1000 ether; + uint256 daiReserve = 600000 ether; + wETH.mint(address(uniswapPair), wethReserve); + dai.mint(address(uniswapPair), daiReserve); + uniswapPair.mint(address(this)); + + // The current batch has a sell order selling 1 wETH and a buy order buying + // 0.5 wETH. This means there is exactly a surplus 0.5 wETH that needs to be + // sold to Uniswap. Uniswap is governed by a balancing equation which can be + // used to compute the exact buy amount for selling the 0.5 wETH and we can + // use to build our the settlement with a smart contract interaction. + // ``` + // (reservewETH + inwETH * 0.997) * (reservedai - outdai) = reservewETH * reservedai + // outdai = (reservedai * inwETH * 0.997) / (reservewETH + inwETH * 0.997) + // = (reservedai * inwETH * 997) / (reservewETH * 1000 + inwETH * 997) + // ``` + uint256 uniswapWethInAmount = 0.5 ether; + uint256 uniswapDaiOutAmount = + daiReserve * uniswapWethInAmount * 997 / ((wethReserve * 1000) + (uniswapWethInAmount * 997)); + + Vm.Wallet memory trader1 = vm.createWallet("trader1"); + Vm.Wallet memory trader2 = vm.createWallet("trader2"); + + // mint some weth + wETH.mint(trader1.addr, 1.001 ether); + vm.prank(trader1.addr); + wETH.approve(vaultRelayer, type(uint256).max); + + // place order to sell 1 wETH for min 500 dai + encoder.signEncodeTrade( + vm, + trader1, + GPv2Order.Data({ + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellToken: wETH, + buyToken: dai, + sellAmount: 1 ether, + buyAmount: 500 ether, + feeAmount: 0.001 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // mint some dai + dai.mint(trader2.addr, 300.3 ether); + vm.prank(trader2.addr); + dai.approve(vaultRelayer, type(uint256).max); + + // place order to buy 0.5 wETH for max 300 dai + encoder.signEncodeTrade( + vm, + trader2, + GPv2Order.Data({ + kind: GPv2Order.KIND_BUY, + partiallyFillable: false, + sellToken: dai, + buyToken: wETH, + sellAmount: 300 ether, + buyAmount: 0.5 ether, + feeAmount: 0.3 ether, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER + }), + domainSeparator, + GPv2Signing.Scheme.Eip712, + 0 + ); + + // interaction to swap the remainder on uniswap + encoder.addInteraction( + GPv2Interaction.Data({ + target: address(wETH), + value: 0, + callData: abi.encodeCall(IERC20.transfer, (address(uniswapPair), uniswapWethInAmount)) + }), + SettlementEncoder.InteractionStage.INTRA + ); + (uint256 amount0Out, uint256 amount1Out) = + isWethToken0 ? (uint256(0), uniswapDaiOutAmount) : (uniswapDaiOutAmount, uint256(0)); + encoder.addInteraction( + GPv2Interaction.Data({ + target: address(uniswapPair), + value: 0, + callData: abi.encodeCall(IUniswapV2Pair.swap, (amount0Out, amount1Out, address(settlement), hex"")) + }), + SettlementEncoder.InteractionStage.INTRA + ); + + // set token prices + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = wETH; + tokens[1] = dai; + uint256[] memory prices = new uint256[](2); + prices[0] = uniswapDaiOutAmount; + prices[1] = uniswapWethInAmount; + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + + vm.prank(solver); + settle(encodedSettlement); + + assertEq(wETH.balanceOf(address(settlement)), 0.001 ether, "weth fees not as expected"); + assertEq(dai.balanceOf(address(settlement)), 0.3 ether, "dai fees not as expected"); + + assertEq(wETH.balanceOf(trader1.addr), 0, "not all weth sold"); + assertEq(dai.balanceOf(trader1.addr), uniswapDaiOutAmount * 2, "dai received not as expected"); + + assertEq(wETH.balanceOf(trader2.addr), 0.5 ether, "weth bought not correct amount"); + assertEq(dai.balanceOf(trader2.addr), 300.3 ether - (uniswapDaiOutAmount + 0.3 ether)); + + uint256 finalWethReserve; + uint256 finalDaiReserve; + + { + (uint256 token0Reserve, uint256 token1Reserve) = uniswapPair.getReserves(); + (finalWethReserve, finalDaiReserve) = + isWethToken0 ? (token0Reserve, token1Reserve) : (token1Reserve, token0Reserve); + } + assertEq(finalWethReserve, wethReserve + uniswapWethInAmount, "weth reserve not as expected"); + assertEq(finalDaiReserve, daiReserve - uniswapDaiOutAmount, "dai reserve not as expected"); + } +} diff --git a/test/e2e/UpgradeAuthenticator.t.sol b/test/e2e/UpgradeAuthenticator.t.sol new file mode 100644 index 00000000..b1319696 --- /dev/null +++ b/test/e2e/UpgradeAuthenticator.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {GPv2AllowListAuthentication} from "src/contracts/GPv2AllowListAuthentication.sol"; + +import {Helper} from "./Helper.sol"; + +interface IEIP173Proxy { + function upgradeTo(address) external; + function transferOwnership(address) external; + function owner() external view returns (address); +} + +contract GPv2AllowListAuthenticationV2 is GPv2AllowListAuthentication { + function newMethod() external pure returns (uint256) { + return 1337; + } +} + +contract UpgradeAuthenticatorTest is Helper(false) { + GPv2AllowListAuthenticationV2 v2Impl; + + function setUp() public override { + super.setUp(); + v2Impl = new GPv2AllowListAuthenticationV2(); + } + + function test_should_upgrade_authenticator() external { + vm.expectRevert(); + GPv2AllowListAuthenticationV2(address(authenticator)).newMethod(); + + vm.prank(owner); + IEIP173Proxy(address(authenticator)).upgradeTo(address(v2Impl)); + + assertEq( + GPv2AllowListAuthenticationV2(address(authenticator)).newMethod(), 1337, "proxy didnt update as expected" + ); + } + + function test_should_preserve_storage() external { + address newSolver = makeAddr("newSolver"); + address newManager = makeAddr("newManager"); + + vm.startPrank(owner); + GPv2AllowListAuthentication(address(authenticator)).addSolver(newSolver); + GPv2AllowListAuthentication(address(authenticator)).setManager(newManager); + + IEIP173Proxy(address(authenticator)).upgradeTo(address(v2Impl)); + vm.stopPrank(); + + assertEq(authenticator.isSolver(newSolver), true, "solver not retained in storage after proxy upgrade"); + assertEq( + GPv2AllowListAuthentication(address(authenticator)).manager(), + newManager, + "manager not retained in storage after proxy upgrade" + ); + } + + function test_should_allow_proxy_owner_to_change_manager() external { + // transfer ownership to a new address and then assert the behavior + // to have a proxy owner that is different address than manager + address newOwner = makeAddr("newOwner"); + vm.prank(owner); + IEIP173Proxy(address(authenticator)).transferOwnership(newOwner); + + address newManager = makeAddr("newManager"); + vm.prank(newOwner); + GPv2AllowListAuthentication(address(authenticator)).setManager(newManager); + + assertEq( + GPv2AllowListAuthentication(address(authenticator)).manager(), + newManager, + "proxy owner couldnt update manager" + ); + } + + function test_should_be_able_to_transfer_proxy_ownership() external { + address newOwner = makeAddr("newOwner"); + vm.prank(owner); + IEIP173Proxy(address(authenticator)).transferOwnership(newOwner); + + assertEq(IEIP173Proxy(address(authenticator)).owner(), newOwner, "ownership didnt transfer as expected"); + } + + function test_should_revert_when_upgrading_with_the_authentication_manager() external { + address newManager = makeAddr("newManager"); + vm.prank(owner); + GPv2AllowListAuthentication(address(authenticator)).setManager(newManager); + + vm.prank(newManager); + vm.expectRevert("NOT_AUTHORIZED"); + IEIP173Proxy(address(authenticator)).upgradeTo(address(v2Impl)); + } + + function test_should_revert_when_not_upgrading_with_the_proxy_owner() external { + address nobody = makeAddr("nobody"); + vm.prank(nobody); + vm.expectRevert("NOT_AUTHORIZED"); + IEIP173Proxy(address(authenticator)).upgradeTo(address(v2Impl)); + } +} diff --git a/test/e2e/WETH9.sol b/test/e2e/WETH9.sol new file mode 100644 index 00000000..71f4586a --- /dev/null +++ b/test/e2e/WETH9.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +contract WETH9 { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + receive() external payable { + deposit(); + } + + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + payable(msg.sender).transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint256) { + return address(this).balance; + } + + function approve(address guy, uint256 wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint256 wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint256 wad) public returns (bool) { + require(balanceOf[src] >= wad); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= wad); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} diff --git a/test/e2e/WineOilMarket.t.sol b/test/e2e/WineOilMarket.t.sol new file mode 100644 index 00000000..7a73423f --- /dev/null +++ b/test/e2e/WineOilMarket.t.sol @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Vm.sol"; + +import {IERC20} from "src/contracts/interfaces/IERC20.sol"; + +import {GPv2Order, GPv2Signing, SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol"; +import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol"; +import {Helper, IERC20Mintable} from "./Helper.sol"; + +using SettlementEncoder for SettlementEncoder.State; +using TokenRegistry for TokenRegistry.State; +using TokenRegistry for Registry; + +contract WineOilTest is Helper(false) { + IERC20Mintable eur; + IERC20Mintable oil; + IERC20Mintable wine; + + uint256 constant STARTING_BALANCE = 1000 ether; + + function setUp() public override { + super.setUp(); + + eur = deployMintableErc20("eur", "eur"); + oil = deployMintableErc20("oil", "oil"); + wine = deployMintableErc20("wine", "wine"); + } + + // Settlement for the RetrETH wine and olive oil market: + // + // /---(6. BUY 10 🍷 with 💶 if p(🍷) <= 13)--> [🍷] + // | | + // | | + // [💶] (1. SELL 12 🍷 for 🫒 if p(🍷) >= p(🫒)) + // |^ | + // || | + // |\--(4. SELL 15 🫒 for 💶 if p(🫒) >= 12)--\ v + // \---(5. BUY 4 🫒 with 💶 if p(🫒) <= 13)---> [🫒] + function test_should_settle_red_wine_and_olive_oil_market() external { + Vm.Wallet memory trader1 = vm.createWallet("trader1"); + Vm.Wallet memory trader2 = vm.createWallet("trader2"); + Vm.Wallet memory trader3 = vm.createWallet("trader3"); + Vm.Wallet memory trader4 = vm.createWallet("trader4"); + uint256 feeAmount = 1 ether; + + // sell 12 wine for min 12 oil + _createOrder( + trader1, + _orderData({ + sellToken: wine, + buyToken: oil, + sellAmount: 12 ether, + buyAmount: 12 ether, + feeAmount: feeAmount, + orderKind: GPv2Order.KIND_SELL, + partiallyFillable: false + }), + 0 + ); + // sell 15 oil for min 180 eur + _createOrder( + trader2, + _orderData({ + sellToken: oil, + buyToken: eur, + sellAmount: 15 ether, + buyAmount: 180 ether, + feeAmount: feeAmount, + orderKind: GPv2Order.KIND_SELL, + partiallyFillable: false + }), + 0 + ); + // buy 4 oil with max 52 eur + uint256 order3ExecutedAmount = uint256(27 ether) / 13; + _createOrder( + trader3, + _orderData({ + sellToken: eur, + buyToken: oil, + sellAmount: 52 ether, + buyAmount: 4 ether, + feeAmount: feeAmount, + orderKind: GPv2Order.KIND_BUY, + partiallyFillable: true + }), + order3ExecutedAmount + ); + // buy 20 wine with max 280 eur + uint256 order4ExecutedAmount = 12 ether; + _createOrder( + trader4, + _orderData({ + sellToken: eur, + buyToken: wine, + sellAmount: 280 ether, + buyAmount: 20 ether, + feeAmount: feeAmount, + orderKind: GPv2Order.KIND_BUY, + partiallyFillable: true + }), + order4ExecutedAmount + ); + + uint256 oilPrice = 13 ether; + uint256 winePrice = 14 ether; + { + // set token prices + IERC20[] memory tokens = new IERC20[](3); + tokens[0] = eur; + tokens[1] = oil; + tokens[2] = wine; + uint256[] memory prices = new uint256[](3); + prices[0] = 1 ether; + prices[1] = oilPrice; + prices[2] = winePrice; + + encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices); + } + + // settle the orders + SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement); + vm.prank(solver); + settle(encodedSettlement); + + assertEq( + wine.balanceOf(trader1.addr), + STARTING_BALANCE - 12 ether - feeAmount, + "trader1 sold token amounts not as expected" + ); + uint256 trader1AmountOut = ceilDiv(uint256(12 ether * 14 ether), 13 ether); + assertEq(oil.balanceOf(trader1.addr), trader1AmountOut, "trader1 amountOut not as expected"); + + assertEq( + oil.balanceOf(trader2.addr), + STARTING_BALANCE - 15 ether - feeAmount, + "trader2 sold token amounts not as expected" + ); + assertEq(eur.balanceOf(trader2.addr), 15 ether * 13, "trader2 amountOut not as expected"); + + // order: buy 4 oil with max 52 eur, partial execution + uint256 order3SellAmount = order3ExecutedAmount * oilPrice / 1 ether; + uint256 order3FeeAmount = feeAmount * order3ExecutedAmount / 4 ether; + assertEq( + eur.balanceOf(trader3.addr), + STARTING_BALANCE - order3SellAmount - order3FeeAmount, + "trader3 sold token amount not as expected" + ); + assertEq(oil.balanceOf(trader3.addr), order3ExecutedAmount, "trader3 amountOut not as expected"); + + // order: buy 20 wine with max 280 eur, partial execution + uint256 order4SellAmount = order4ExecutedAmount * winePrice / 1 ether; + uint256 order4FeeAmount = feeAmount * order4ExecutedAmount / 20 ether; + assertEq( + eur.balanceOf(trader4.addr), + STARTING_BALANCE - order4SellAmount - order4FeeAmount, + "trader4 sold token amount not as expected" + ); + assertEq(wine.balanceOf(trader4.addr), order4ExecutedAmount, "trader4 amountOut not as expected"); + } + + function _createOrder(Vm.Wallet memory wallet, GPv2Order.Data memory order, uint256 executedAmount) internal { + IERC20Mintable(address(order.sellToken)).mint(wallet.addr, STARTING_BALANCE); + vm.prank(wallet.addr); + order.sellToken.approve(vaultRelayer, type(uint256).max); + + encoder.signEncodeTrade(vm, wallet, order, domainSeparator, GPv2Signing.Scheme.Eip712, executedAmount); + } + + function _orderData( + IERC20 sellToken, + IERC20 buyToken, + uint256 sellAmount, + uint256 buyAmount, + uint256 feeAmount, + bytes32 orderKind, + bool partiallyFillable + ) internal pure returns (GPv2Order.Data memory order) { + order = GPv2Order.Data({ + sellToken: sellToken, + buyToken: buyToken, + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER, + sellAmount: sellAmount, + buyAmount: buyAmount, + validTo: 0xffffffff, + appData: bytes32(uint256(1)), + feeAmount: feeAmount, + kind: orderKind, + partiallyFillable: partiallyFillable, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }); + } + + function ceilDiv(uint256 num, uint256 den) internal pure returns (uint256) { + return num % den == 0 ? num / den : (num / den) + 1; + } +} diff --git a/test/e2e/balancerSwap.test.ts b/test/e2e/balancerSwap.test.ts deleted file mode 100644 index eeb38757..00000000 --- a/test/e2e/balancerSwap.test.ts +++ /dev/null @@ -1,488 +0,0 @@ -import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json"; -import { expect } from "chai"; -import Debug from "debug"; -import { BigNumberish, Contract, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import MockPool from "../../balancer/test/MockPool.json"; -import { - OrderBalance, - OrderKind, - SwapEncoder, - SigningScheme, - TypedDataDomain, - domain, - grantRequiredRoles, -} from "../../src/ts"; -import { UserBalanceOpKind } from "../balancer"; - -import { deployTestContracts } from "./fixture"; - -enum BalancerErrors { - SWAP_LIMIT = "BAL#507", - SWAP_DEADLINE = "BAL#508", -} - -const LOTS = ethers.utils.parseEther("10000.0"); -const debug = Debug("e2e:balancerSwap"); - -describe("E2E: Direct Balancer swap", () => { - let deployer: Wallet; - let solver: Wallet; - let pooler: Wallet; - let trader: Wallet; - - let vault: Contract; - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - let tokens: [Contract, Contract, Contract]; - let pools: Record; - - let snapshot: unknown; - - before(async () => { - const deployment = await deployTestContracts(); - - ({ - deployer, - vault, - settlement, - vaultRelayer, - wallets: [solver, pooler, trader], - } = deployment); - - const { vaultAuthorizer, authenticator, manager } = deployment; - await grantRequiredRoles( - vaultAuthorizer.connect(manager), - vault.address, - vaultRelayer.address, - ); - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - - tokens = [ - await waffle.deployContract(deployer, ERC20, ["TOK1", 18]), - await waffle.deployContract(deployer, ERC20, ["TOK2", 18]), - await waffle.deployContract(deployer, ERC20, ["TOK3", 18]), - ]; - - pools = {}; - for (let i = 0; i < tokens.length; i++) { - const [token0, token1] = [tokens[i], tokens[(i + 1) % tokens.length]]; - const [tokenA, tokenB] = - token0.address.toLowerCase() < token1.address.toLowerCase() - ? [token0, token1] - : [token1, token0]; - - const TWO_TOKEN_SPECIALIZATION = 2; - const pool = await waffle.deployContract(deployer, MockPool, [ - vault.address, - TWO_TOKEN_SPECIALIZATION, - ]); - await pool.registerTokens( - [tokenA.address, tokenB.address], - [ethers.constants.AddressZero, ethers.constants.AddressZero], - ); - - for (const token of [tokenA, tokenB]) { - await token.mint(pooler.address, LOTS); - await token - .connect(pooler) - .approve(vault.address, ethers.constants.MaxUint256); - } - await vault - .connect(pooler) - .joinPool(await pool.getPoolId(), pooler.address, pooler.address, { - assets: [tokenA.address, tokenB.address], - maxAmountsIn: [LOTS, LOTS], - // NOTE: The mock pool uses this for encoding the pool share amounts - // that a user (here `pooler`) gets when joining the pool (first value) - // as well as the pool fees (second value). - userData: ethers.utils.defaultAbiCoder.encode( - ["uint256[]", "uint256[]"], - [ - [LOTS, LOTS], - [0, 0], - ], - ), - fromInternalBalance: false, - }); - - pools[`${tokenA.address}-${tokenB.address}`] = pool; - pools[`${tokenB.address}-${tokenA.address}`] = pool; - } - - snapshot = await ethers.provider.send("evm_snapshot", []); - }); - - beforeEach(async () => { - // NOTE: Use EVM snapshots to speed up test execution, as the setup time is - // quite high (around 1s **per test**). Oddly, snapshots need to be - // re-created every time they are reverted. - await ethers.provider.send("evm_revert", [snapshot]); - snapshot = await ethers.provider.send("evm_snapshot", []); - }); - - const poolFor = (tokenA: Contract, tokenB: Contract) => { - return pools[`${tokenA.address}-${tokenB.address}`]; - }; - - const mintAndApprove = async ( - trader: Wallet, - token: Contract, - amount: BigNumberish, - balance = OrderBalance.ERC20, - ) => { - await token.mint(trader.address, amount); - // NOTE: For now, approve both the Vault and the Vault relayer since we do - // not distinguish between `ERC20` and `EXTERNAL` balance configurations. - await token - .connect(trader) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await token - .connect(trader) - .approve(vault.address, ethers.constants.MaxUint256); - if (balance == OrderBalance.INTERNAL) { - await vault.connect(trader).manageUserBalance([ - { - kind: UserBalanceOpKind.DEPOSIT_INTERNAL, - asset: token.address, - amount, - sender: trader.address, - recipient: trader.address, - }, - ]); - } - await vault - .connect(trader) - .setRelayerApproval(trader.address, vaultRelayer.address, true); - }; - - const balanceOf = async ( - { address }: Wallet | Contract, - token: Contract, - balance = OrderBalance.ERC20, - ) => { - if (balance == OrderBalance.INTERNAL) { - const [balance] = await vault.getInternalBalance(address, [ - token.address, - ]); - return balance; - } else { - return await token.balanceOf(address); - } - }; - - const balanceVariants = [ - OrderBalance.ERC20, - OrderBalance.EXTERNAL, - OrderBalance.INTERNAL, - ].flatMap((sellTokenBalance) => - [OrderBalance.ERC20, OrderBalance.INTERNAL].map((buyTokenBalance) => { - return { - name: `${sellTokenBalance} to ${buyTokenBalance}`, - sellTokenBalance, - buyTokenBalance, - }; - }), - ); - - for (const kind of [OrderKind.SELL, OrderKind.BUY]) { - for (const { name, ...balances } of balanceVariants) { - it(`performs Balancer swap for ${name} ${kind} order`, async () => { - await mintAndApprove( - trader, - tokens[0], - ethers.utils.parseEther("100.1"), - balances.sellTokenBalance, - ); - - const pool = poolFor(tokens[0], tokens[1]); - // NOTE: Set a fixed multiplier used for computing the exchange rate for - // the mock pool. In the wild, this would depend on the current state of - // the pool. - await pool.setMultiplier(ethers.utils.parseEther("0.9")); - - const encoder = new SwapEncoder(domainSeparator); - await encoder.signEncodeTrade( - { - sellToken: tokens[0].address, - buyToken: tokens[1].address, - sellAmount: ethers.utils.parseEther("100.0"), - buyAmount: ethers.utils.parseEther("72.0"), - feeAmount: ethers.utils.parseEther("0.1"), - validTo: 0xffffffff, - appData: 0, - partiallyFillable: false, - kind, - ...balances, - }, - trader, - SigningScheme.EIP712, - ); - encoder.encodeSwapStep({ - poolId: await pool.getPoolId(), - assetIn: tokens[0].address, - assetOut: tokens[1].address, - amount: - kind == OrderKind.SELL - ? ethers.utils.parseEther("100.0") - : ethers.utils.parseEther("72.0"), - }); - - const tx = await settlement - .connect(solver) - .swap(...encoder.encodedSwap()); - - const { gasUsed } = await tx.wait(); - debug(`${name} gas: ${gasUsed}`); - - const sellTokenBalance = await balanceOf( - trader, - tokens[0], - balances.sellTokenBalance, - ); - const buyTokenBalance = await balanceOf( - trader, - tokens[1], - balances.buyTokenBalance, - ); - - // NOTE: User keeps positive surplus! - if (kind == OrderKind.SELL) { - expect(sellTokenBalance).to.equal(ethers.constants.Zero); - expect(buyTokenBalance).to.equal(ethers.utils.parseEther("90.0")); - } else { - expect(sellTokenBalance).to.equal(ethers.utils.parseEther("20.0")); - expect(buyTokenBalance).to.equal(ethers.utils.parseEther("72.0")); - } - }); - } - - it(`reverts ${kind} order if fill-or-kill amount is not respected`, async () => { - await mintAndApprove(trader, tokens[0], ethers.utils.parseEther("100.1")); - - const pool = poolFor(tokens[0], tokens[1]); - // NOTE: Set a very favourable multiplier for the swap. - await pool.setMultiplier(ethers.utils.parseEther("2.0")); - - const encoder = new SwapEncoder(domainSeparator); - await encoder.signEncodeTrade( - { - sellToken: tokens[0].address, - buyToken: tokens[1].address, - sellAmount: ethers.utils.parseEther("100.0"), - buyAmount: ethers.utils.parseEther("100.0"), - feeAmount: ethers.utils.parseEther("0.1"), - validTo: 0xffffffff, - appData: 0, - // NOTE: Partially fillable or not, it doesn't matter as the - // "fast-path" treats all orders as fill-or-kill orders. - partiallyFillable: true, - kind, - }, - trader, - SigningScheme.EIP712, - ); - encoder.encodeSwapStep({ - poolId: await pool.getPoolId(), - assetIn: tokens[0].address, - assetOut: tokens[1].address, - // NOTE: Set "better" amounts, where we pay less and get more. These, - // however, should still cause a revert as they aren't the exact amounts - // that were requested in the orders. - amount: - kind == OrderKind.SELL - ? ethers.utils.parseEther("99.0") - : ethers.utils.parseEther("101.0"), - }); - - await expect( - settlement.connect(solver).swap(...encoder.encodedSwap()), - ).to.be.revertedWith(`${kind} amount not respected`); - }); - - it(`reverts ${kind} order if limit price is not respected`, async () => { - await mintAndApprove(trader, tokens[0], ethers.utils.parseEther("100.1")); - - const pool = poolFor(tokens[0], tokens[1]); - // NOTE: Set a multiplier that satisfies the order's limit price but not - // the specified limit amount. - await pool.setMultiplier(ethers.utils.parseEther("1.1")); - - const encoder = new SwapEncoder(domainSeparator); - await encoder.signEncodeTrade( - { - sellToken: tokens[0].address, - buyToken: tokens[1].address, - sellAmount: ethers.utils.parseEther("100.0"), - buyAmount: ethers.utils.parseEther("100.0"), - feeAmount: ethers.utils.parseEther("0.1"), - validTo: 0xffffffff, - appData: 0, - partiallyFillable: false, - kind, - }, - trader, - SigningScheme.EIP712, - { - limitAmount: - kind == OrderKind.SELL - ? ethers.utils.parseEther("120.0") - : ethers.utils.parseEther("80.0"), - }, - ); - encoder.encodeSwapStep({ - poolId: await pool.getPoolId(), - assetIn: tokens[0].address, - assetOut: tokens[1].address, - amount: ethers.utils.parseEther("100.0"), - }); - - await expect( - settlement.connect(solver).swap(...encoder.encodedSwap()), - ).to.be.revertedWith(BalancerErrors.SWAP_LIMIT); - }); - } - - it("reverts if order is expired", async () => { - const { timestamp } = await ethers.provider.getBlock("latest"); - - await mintAndApprove(trader, tokens[0], ethers.utils.parseEther("100.1")); - - const encoder = new SwapEncoder(domainSeparator); - await encoder.signEncodeTrade( - { - sellToken: tokens[0].address, - buyToken: tokens[1].address, - sellAmount: ethers.utils.parseEther("100.0"), - buyAmount: ethers.utils.parseEther("72.0"), - feeAmount: ethers.utils.parseEther("0.1"), - validTo: timestamp - 1, - appData: 0, - kind: OrderKind.SELL, - partiallyFillable: false, - }, - trader, - SigningScheme.EIP712, - ); - - await expect( - settlement.connect(solver).swap(...encoder.encodedSwap()), - ).to.be.revertedWith(BalancerErrors.SWAP_DEADLINE); - }); - - it("allows using liquidity from multiple pools", async () => { - await mintAndApprove(trader, tokens[0], ethers.utils.parseEther("100.1")); - - const encoder = new SwapEncoder(domainSeparator); - await encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - sellToken: tokens[0].address, - buyToken: tokens[2].address, - sellAmount: ethers.utils.parseEther("100.0"), - buyAmount: ethers.utils.parseEther("125.0"), - feeAmount: ethers.utils.parseEther("0.1"), - validTo: 0xffffffff, - appData: 0, - partiallyFillable: false, - }, - trader, - SigningScheme.EIP712, - ); - - // NOTE: Use liquidity by performing a multi-hop swap from `0 -> 1 -> 2`. - await poolFor(tokens[0], tokens[1]).setMultiplier( - ethers.utils.parseEther("1.1"), - ); - encoder.encodeSwapStep({ - poolId: await poolFor(tokens[0], tokens[1]).getPoolId(), - assetIn: tokens[0].address, - assetOut: tokens[1].address, - amount: ethers.utils.parseEther("70.0"), - }); - await poolFor(tokens[1], tokens[2]).setMultiplier( - ethers.utils.parseEther("1.2"), - ); - encoder.encodeSwapStep({ - poolId: await poolFor(tokens[1], tokens[2]).getPoolId(), - assetIn: tokens[1].address, - assetOut: tokens[2].address, - // NOTE: Setting amount to zero indicates a "multi-hop" swap and uses the - // computed `amountOut` of the previous swap. - amount: ethers.constants.Zero, - }); - // NOTE: Also use liquidity from a direct `0 -> 2` pool. - await poolFor(tokens[0], tokens[2]).setMultiplier( - ethers.utils.parseEther("1.3"), - ); - encoder.encodeSwapStep({ - poolId: await poolFor(tokens[0], tokens[2]).getPoolId(), - assetIn: tokens[0].address, - assetOut: tokens[2].address, - amount: ethers.utils.parseEther("30.0"), - }); - - await settlement.connect(solver).swap(...encoder.encodedSwap()); - - // NOTE: Sold 70 for 1.1*1.2 and 30 for 1.3, so should receive 131.4. - expect(await balanceOf(trader, tokens[2])).to.equal( - ethers.utils.parseEther("131.4"), - ); - }); - - it("allows multi-hop buy orders", async () => { - await mintAndApprove(trader, tokens[0], ethers.utils.parseEther("13.1")); - - const encoder = new SwapEncoder(domainSeparator); - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - buyToken: tokens[2].address, - sellToken: tokens[0].address, - buyAmount: ethers.utils.parseEther("100.0"), - sellAmount: ethers.utils.parseEther("13.0"), - feeAmount: ethers.utils.parseEther("0.1"), - validTo: 0xffffffff, - appData: 0, - partiallyFillable: false, - }, - trader, - SigningScheme.EIP712, - ); - - // NOTE: Use liquidity by performing a multi-hop swap from `2 -> 1 -> 0`. - await poolFor(tokens[2], tokens[1]).setMultiplier( - ethers.utils.parseEther("4.0"), - ); - encoder.encodeSwapStep({ - poolId: await poolFor(tokens[2], tokens[1]).getPoolId(), - assetOut: tokens[2].address, - assetIn: tokens[1].address, - amount: ethers.utils.parseEther("100.0"), - }); - await poolFor(tokens[1], tokens[0]).setMultiplier( - ethers.utils.parseEther("2.0"), - ); - encoder.encodeSwapStep({ - poolId: await poolFor(tokens[1], tokens[0]).getPoolId(), - assetOut: tokens[1].address, - assetIn: tokens[0].address, - // NOTE: Setting amount to zero indicates a "multi-hop" swap and uses the - // computed `amountIn` of the previous swap. - amount: ethers.constants.Zero, - }); - - await settlement.connect(solver).swap(...encoder.encodedSwap()); - - // NOTE: Bought 100 for 4.0*2.0, so should pay 12.5. - expect(await balanceOf(trader, tokens[0])).to.equal( - ethers.utils.parseEther("0.5"), - ); - }); -}); diff --git a/test/e2e/burnFees.test.ts b/test/e2e/burnFees.test.ts deleted file mode 100644 index af76b2d0..00000000 --- a/test/e2e/burnFees.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json"; -import { expect } from "chai"; -import { Contract, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import { - InteractionStage, - OrderKind, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, -} from "../../src/ts"; - -import { deployTestContracts } from "./fixture"; - -describe("E2E: Burn fees", () => { - let deployer: Wallet; - let solver: Wallet; - let traders: Wallet[]; - - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - let owl: Contract; - let dai: Contract; - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - deployer, - settlement, - vaultRelayer, - wallets: [solver, ...traders], - } = 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", "Owl token"]); - dai = await waffle.deployContract(deployer, ERC20, ["DAI", "Dai token"]); - }); - - it("uses post-interation to burn settlement fees", async () => { - // Settle a trivial 1:1 trade between DAI and OWL. - - const ONE_USD = ethers.utils.parseEther("1.0"); - - const encoder = new SettlementEncoder(domainSeparator); - - await owl.mint(traders[0].address, ONE_USD.mul(1001)); - await owl - .connect(traders[0]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: owl.address, - buyToken: dai.address, - sellAmount: ONE_USD.mul(1000), - buyAmount: ONE_USD.mul(1000), - feeAmount: ONE_USD, - validTo: 0xffffffff, - appData: 1, - }, - traders[0], - SigningScheme.EIP712, - ); - - await dai.mint(traders[1].address, ONE_USD.mul(1000)); - await dai - .connect(traders[1]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - partiallyFillable: false, - buyToken: owl.address, - sellToken: dai.address, - buyAmount: ONE_USD.mul(1000), - sellAmount: ONE_USD.mul(1000), - feeAmount: ethers.constants.Zero, - validTo: 0xffffffff, - appData: 2, - }, - traders[1], - SigningScheme.EIP712, - ); - - encoder.encodeInteraction( - { - target: owl.address, - callData: owl.interface.encodeFunctionData("burn", [ONE_USD]), - }, - InteractionStage.POST, - ); - - const tx = settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [owl.address]: 1, - [dai.address]: 1, - }), - ); - - await expect(tx) - .to.emit(owl, "Transfer") - .withArgs(settlement.address, ethers.constants.AddressZero, ONE_USD); - expect(await dai.balanceOf(settlement.address)).to.deep.equal( - ethers.constants.Zero, - ); - }); -}); diff --git a/test/e2e/buyEth.test.ts b/test/e2e/buyEth.test.ts deleted file mode 100644 index bfcb6eda..00000000 --- a/test/e2e/buyEth.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json"; -import { expect } from "chai"; -import { Contract, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import { - BUY_ETH_ADDRESS, - OrderKind, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, -} from "../../src/ts"; - -import { deployTestContracts } from "./fixture"; - -describe("E2E: Buy Ether", () => { - let deployer: Wallet; - let solver: Wallet; - let traders: Wallet[]; - - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - let weth: Contract; - let usdt: Contract; - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - deployer, - weth, - settlement, - vaultRelayer, - wallets: [solver, ...traders], - } = deployment); - - const { authenticator, manager } = deployment; - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - - usdt = await waffle.deployContract(deployer, ERC20, ["USDT", 6]); - }); - - it("should unwrap WETH for orders buying Ether", async () => { - // Settle a trivial batch between two overlapping trades: - // - // /----(1. SELL 1 WETH for USDT if p(WETH) >= 1100)----\ - // | v - // [USDT] [(W)ETH] - // ^ | - // \-----(2. BUY 1 ETH for USDT if p(WETH) <= 1200)-----/ - - const encoder = new SettlementEncoder(domainSeparator); - - await weth - .connect(traders[0]) - .deposit({ value: ethers.utils.parseEther("1.001") }); - await weth - .connect(traders[0]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: weth.address, - buyToken: usdt.address, - sellAmount: ethers.utils.parseEther("1.0"), - buyAmount: ethers.utils.parseUnits("1100.0", 6), - feeAmount: ethers.utils.parseEther("0.001"), - validTo: 0xffffffff, - appData: 1, - }, - traders[0], - SigningScheme.EIP712, - ); - - await usdt.mint(traders[1].address, ethers.utils.parseUnits("1201.2", 6)); - await usdt - .connect(traders[1]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - partiallyFillable: false, - buyToken: BUY_ETH_ADDRESS, - sellToken: usdt.address, - buyAmount: ethers.utils.parseEther("1.0"), - sellAmount: ethers.utils.parseUnits("1200.0", 6), - feeAmount: ethers.utils.parseUnits("1.2", 6), - validTo: 0xffffffff, - appData: 2, - }, - traders[1], - SigningScheme.EIP712, - ); - - encoder.encodeInteraction({ - target: weth.address, - callData: weth.interface.encodeFunctionData("withdraw", [ - ethers.utils.parseEther("1.0"), - ]), - }); - - const trader1InitialBalance = await traders[1].getBalance(); - await settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [weth.address]: ethers.utils.parseUnits("1150.0", 6), - [BUY_ETH_ADDRESS]: ethers.utils.parseUnits("1150.0", 6), - [usdt.address]: ethers.utils.parseEther("1.0"), - }), - ); - - expect(await weth.balanceOf(settlement.address)).to.deep.equal( - ethers.utils.parseEther("0.001"), - ); - expect(await weth.balanceOf(traders[0].address)).to.deep.equal( - ethers.constants.Zero, - ); - expect(await traders[1].getBalance()).to.deep.equal( - trader1InitialBalance.add(ethers.utils.parseEther("1.0")), - ); - }); -}); diff --git a/test/e2e/contractOrdersWithGnosisSafe.test.ts b/test/e2e/contractOrdersWithGnosisSafe.test.ts deleted file mode 100644 index 9d5a6093..00000000 --- a/test/e2e/contractOrdersWithGnosisSafe.test.ts +++ /dev/null @@ -1,302 +0,0 @@ -import GnosisSafe from "@gnosis.pm/safe-contracts/build/artifacts/contracts/GnosisSafe.sol/GnosisSafe.json"; -import CompatibilityFallbackHandler from "@gnosis.pm/safe-contracts/build/artifacts/contracts/handler/CompatibilityFallbackHandler.sol/CompatibilityFallbackHandler.json"; -import GnosisSafeProxyFactory from "@gnosis.pm/safe-contracts/build/artifacts/contracts/proxies/GnosisSafeProxyFactory.sol/GnosisSafeProxyFactory.json"; -import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json"; -import { expect } from "chai"; -import { BytesLike, Signer, Contract, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import { - EIP1271_MAGICVALUE, - OrderKind, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, - hashOrder, -} from "../../src/ts"; - -import { deployTestContracts } from "./fixture"; - -interface SafeTransaction { - to: string; - data: BytesLike; -} - -class GnosisSafeManager { - constructor( - readonly deployer: Signer, - readonly masterCopy: Contract, - readonly signingFallback: Contract, - readonly proxyFactory: Contract, - ) {} - - static async init(deployer: Signer): Promise { - const masterCopy = await waffle.deployContract(deployer, GnosisSafe); - const proxyFactory = await waffle.deployContract( - deployer, - GnosisSafeProxyFactory, - ); - const signingFallback = await waffle.deployContract( - deployer, - CompatibilityFallbackHandler, - ); - return new GnosisSafeManager( - deployer, - masterCopy, - signingFallback, - proxyFactory, - ); - } - - async newSafe( - owners: string[], - threshold: number, - fallback = ethers.constants.AddressZero, - ): Promise { - const proxyAddress = await this.proxyFactory.callStatic.createProxy( - this.masterCopy.address, - "0x", - ); - await this.proxyFactory.createProxy(this.masterCopy.address, "0x"); - const safe = await ethers.getContractAt(GnosisSafe.abi, proxyAddress); - await safe.setup( - owners, - threshold, - ethers.constants.AddressZero, - "0x", - fallback, - ethers.constants.AddressZero, - 0, - ethers.constants.AddressZero, - ); - return safe; - } -} - -async function gnosisSafeSign( - message: BytesLike, - signers: Signer[], -): Promise { - // https://docs.gnosis.io/safe/docs/contracts_signatures/ - const signerAddresses = await Promise.all( - signers.map(async (signer) => (await signer.getAddress()).toLowerCase()), - ); - const sortedSigners = signers - .map((_, index) => index) - .sort((lhs, rhs) => - signerAddresses[lhs] < signerAddresses[rhs] - ? -1 - : signerAddresses[lhs] > signerAddresses[rhs] - ? 1 - : 0, - ) - .map((index) => signers[index]); - - async function encodeEcdsaSignature( - message: BytesLike, - signer: Signer, - ): Promise { - const sig = await signer.signMessage(ethers.utils.arrayify(message)); - const { r, s, v } = ethers.utils.splitSignature(sig); - return ethers.utils.hexConcat([r, s, [v + 4]]); - } - return ethers.utils.hexConcat( - await Promise.all( - sortedSigners.map( - async (signer) => await encodeEcdsaSignature(message, signer), - ), - ), - ); -} - -async function execSafeTransaction( - safe: Contract, - transaction: SafeTransaction, - signers: Signer[], -): Promise { - // most parameters are not needed for this test - const transactionParameters = [ - transaction.to, - 0, - transaction.data, - 0, - 0, - 0, - 0, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - ]; - const nonce = await safe.nonce(); - const message = await safe.getTransactionHash( - ...transactionParameters, - nonce, - ); - const sigs = await gnosisSafeSign(message, signers); - await safe.execTransaction(...transactionParameters, sigs); -} - -async function fallbackSign( - safeAsFallback: Contract, - message: BytesLike, - signers: Signer[], -): Promise { - const safeMessage = await safeAsFallback.getMessageHash( - ethers.utils.defaultAbiCoder.encode(["bytes32"], [message]), - ); - return gnosisSafeSign(safeMessage, signers); -} - -describe("E2E: Order From A Gnosis Safe", () => { - let deployer: Wallet; - let solver: Wallet; - let trader: Wallet; - let safeOwners: Wallet[]; - - let settlement: Contract; - let vaultRelayer: Contract; - let safe: Contract; - let domainSeparator: TypedDataDomain; - let gnosisSafeManager: GnosisSafeManager; - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - deployer, - settlement, - vaultRelayer, - wallets: [solver, trader, ...safeOwners], - } = deployment); - - const { authenticator, manager } = deployment; - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - - gnosisSafeManager = await GnosisSafeManager.init(deployer); - safe = await gnosisSafeManager.newSafe( - safeOwners.map((wallet) => wallet.address), - 2, - gnosisSafeManager.signingFallback.address, - ); - }); - - it("should settle matching orders", async () => { - // EOA trader: sell 1 WETH for 900 DAI - // Safe: buy 1 WETH for 1100 DAI - // Settlement price at 1000 DAI for 1 WETH. - - const erc20 = (symbol: string) => - waffle.deployContract(deployer, ERC20, [symbol, 18]); - - const dai = await erc20("DAI"); - const weth = await erc20("WETH"); - - const UNLIMITED_VALID_TO = 0xffffffff; - const encoder = new SettlementEncoder(domainSeparator); - - const TRADER_FEE = ethers.utils.parseEther("0.001"); - const TRADER_SOLD_AMOUNT = ethers.utils.parseEther("1.0"); - const TRADER_BOUGHT_AMOUNT = ethers.utils.parseEther("900.0"); - - await weth.mint(trader.address, TRADER_SOLD_AMOUNT.add(TRADER_FEE)); - await weth - .connect(trader) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - - encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: weth.address, - buyToken: dai.address, - sellAmount: TRADER_SOLD_AMOUNT, - buyAmount: TRADER_BOUGHT_AMOUNT, - appData: 0, - validTo: UNLIMITED_VALID_TO, - feeAmount: TRADER_FEE, - }, - trader, - SigningScheme.ETHSIGN, - ); - - const SAFE_FEE = ethers.utils.parseEther("10.0"); - const SAFE_SOLD_AMOUNT = ethers.utils.parseEther("1100.0"); - const SAFE_BOUGHT_AMOUNT = ethers.utils.parseEther("1.0"); - - await dai.mint(safe.address, SAFE_SOLD_AMOUNT.add(SAFE_FEE)); - const approveTransaction = { - to: dai.address, - data: dai.interface.encodeFunctionData("approve", [ - vaultRelayer.address, - ethers.constants.MaxUint256, - ]), - }; - await execSafeTransaction(safe, approveTransaction, safeOwners); - expect( - await dai.allowance(safe.address, vaultRelayer.address), - ).to.deep.equal(ethers.constants.MaxUint256); - - const order = { - kind: OrderKind.BUY, - partiallyFillable: false, - sellToken: dai.address, - buyToken: weth.address, - sellAmount: SAFE_SOLD_AMOUNT, - buyAmount: SAFE_BOUGHT_AMOUNT, - appData: 0, - validTo: UNLIMITED_VALID_TO, - feeAmount: SAFE_FEE, - }; - const gpv2Message = hashOrder(domainSeparator, order); - const safeAsFallback = gnosisSafeManager.signingFallback.attach( - safe.address, - ); - // Note: threshold is 2, any two owners should suffice. - const signature = await fallbackSign(safeAsFallback, gpv2Message, [ - safeOwners[4], - safeOwners[2], - ]); - // Note: the fallback handler provides two versions of `isValidSignature` - // for compatibility reasons. The following is the signature of the most - // recent EIP1271-compatible verification function. - const isValidSignature = "isValidSignature(bytes32,bytes)"; - expect( - await safeAsFallback.callStatic[isValidSignature](gpv2Message, signature), - ).to.equal(EIP1271_MAGICVALUE); - - encoder.encodeTrade(order, { - scheme: SigningScheme.EIP1271, - data: { - verifier: safe.address, - signature, - }, - }); - - await settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [dai.address]: ethers.utils.parseEther("1.0"), - [weth.address]: ethers.utils.parseEther("1000.0"), - }), - ); - - expect(await weth.balanceOf(trader.address)).to.deep.equal( - ethers.constants.Zero, - ); - expect(await dai.balanceOf(trader.address)).to.deep.equal( - ethers.utils.parseEther("1000.0"), - ); - - expect(await weth.balanceOf(safe.address)).to.deep.equal( - ethers.utils.parseEther("1.0"), - ); - expect(await dai.balanceOf(safe.address)).to.deep.equal( - SAFE_SOLD_AMOUNT.sub(ethers.utils.parseEther("1000.0")), - ); - - expect(await weth.balanceOf(settlement.address)).to.deep.equal(TRADER_FEE); - expect(await dai.balanceOf(settlement.address)).to.deep.equal(SAFE_FEE); - }); -}); diff --git a/test/e2e/deployment.test.ts b/test/e2e/deployment.test.ts deleted file mode 100644 index 394c2c53..00000000 --- a/test/e2e/deployment.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { expect } from "chai"; -import { Contract, Wallet } from "ethers"; -import { artifacts, ethers } from "hardhat"; -import Proxy from "hardhat-deploy/extendedArtifacts/EIP173Proxy.json"; - -import { - ContractName, - DeploymentArguments, - deterministicDeploymentAddress, - implementationAddress, - proxyInterface, -} from "../../src/ts"; - -import { deployTestContracts } from "./fixture"; - -async function contractAddress( - contractName: C, - ...deploymentArguments: DeploymentArguments -): Promise { - const artifact = await artifacts.readArtifact(contractName); - return deterministicDeploymentAddress(artifact, deploymentArguments); -} - -async function builtAndDeployedMetadataCoincide( - contractAddress: string, - contractName: string, -): Promise { - const contractArtifacts = await artifacts.readArtifact(contractName); - - const code = await ethers.provider.send("eth_getCode", [ - contractAddress, - "latest", - ]); - - // NOTE: The last 53 bytes in a deployed contract's bytecode contains the - // contract metadata. Compare the deployed contract's metadata with the - // compiled contract's metadata. - // - const metadata = (bytecode: string) => bytecode.slice(-106); - - return metadata(code) === metadata(contractArtifacts.deployedBytecode); -} - -describe("E2E: Deployment", () => { - let owner: Wallet; - let manager: Wallet; - let user: Wallet; - - let authenticator: Contract; - let vault: Contract; - let settlement: Contract; - let vaultRelayer: Contract; - - beforeEach(async () => { - ({ - owner, - manager, - wallets: [user], - authenticator, - vault, - settlement, - vaultRelayer, - } = await deployTestContracts()); - - authenticator.connect(user); - settlement.connect(user); - vaultRelayer.connect(user); - }); - - describe("same built and deployed bytecode metadata", () => { - it("authenticator", async () => { - expect( - await builtAndDeployedMetadataCoincide( - await implementationAddress(ethers.provider, authenticator.address), - "GPv2AllowListAuthentication", - ), - ).to.be.true; - }); - - it("settlement", async () => { - expect( - await builtAndDeployedMetadataCoincide( - settlement.address, - "GPv2Settlement", - ), - ).to.be.true; - }); - - it("vaultRelayer", async () => { - expect( - await builtAndDeployedMetadataCoincide( - vaultRelayer.address, - "GPv2VaultRelayer", - ), - ).to.be.true; - }); - }); - - describe("deterministic addresses", () => { - describe("authenticator", () => { - it("proxy", async () => { - expect( - deterministicDeploymentAddress(Proxy, [ - await implementationAddress(ethers.provider, authenticator.address), - owner.address, - authenticator.interface.encodeFunctionData("initializeManager", [ - manager.address, - ]), - ]), - ).to.equal(authenticator.address); - }); - - it("implementation", async () => { - expect(await contractAddress("GPv2AllowListAuthentication")).to.equal( - await implementationAddress(ethers.provider, authenticator.address), - ); - }); - }); - - it("settlement", async () => { - expect( - await contractAddress( - "GPv2Settlement", - authenticator.address, - vault.address, - ), - ).to.equal(settlement.address); - }); - }); - - describe("authorization", () => { - it("authenticator has dedicated owner", async () => { - const proxy = proxyInterface(authenticator); - expect(await proxy.owner()).to.equal(owner.address); - }); - - it("authenticator has dedicated manager", async () => { - expect(await authenticator.manager()).to.equal(manager.address); - }); - }); -}); diff --git a/test/e2e/internalBalances.test.ts b/test/e2e/internalBalances.test.ts deleted file mode 100644 index 8cfdedb6..00000000 --- a/test/e2e/internalBalances.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json"; -import { expect } from "chai"; -import { Contract, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import { - OrderBalance, - OrderKind, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, - grantRequiredRoles, -} from "../../src/ts"; -import { UserBalanceOpKind } from "../balancer"; - -import { deployTestContracts } from "./fixture"; - -describe("E2E: Should allow trading with Vault internal balances", () => { - let deployer: Wallet; - let solver: Wallet; - let traders: Wallet[]; - - let vault: Contract; - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - let tokens: [Contract, Contract]; - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - deployer, - vault, - settlement, - vaultRelayer, - wallets: [solver, ...traders], - } = deployment); - - const { vaultAuthorizer, authenticator, manager } = deployment; - await grantRequiredRoles( - vaultAuthorizer.connect(manager), - vault.address, - vaultRelayer.address, - ); - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - - tokens = [ - await waffle.deployContract(deployer, ERC20, ["T0", 18]), - await waffle.deployContract(deployer, ERC20, ["T1", 18]), - ]; - - await settlement.connect(solver).settle( - ...SettlementEncoder.encodedSetup( - ...tokens.map((token) => ({ - target: token.address, - callData: token.interface.encodeFunctionData("approve", [ - vault.address, - ethers.constants.MaxUint256, - ]), - })), - ), - ); - }); - - it("should settle orders buying and selling with internal balances", async () => { - const encoder = new SettlementEncoder(domainSeparator); - - await tokens[0].mint(traders[0].address, ethers.utils.parseEther("1.001")); - await tokens[0] - .connect(traders[0]) - .approve(vault.address, ethers.constants.MaxUint256); - await vault - .connect(traders[0]) - .setRelayerApproval(traders[0].address, vaultRelayer.address, true); - await encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: tokens[0].address, - buyToken: tokens[1].address, - sellAmount: ethers.utils.parseEther("1.0"), - buyAmount: ethers.utils.parseEther("500.0"), - feeAmount: ethers.utils.parseEther("0.001"), - validTo: 0xffffffff, - appData: 1, - sellTokenBalance: OrderBalance.EXTERNAL, - }, - traders[0], - SigningScheme.EIP712, - ); - - await tokens[1].mint(traders[1].address, ethers.utils.parseEther("300.3")); - await tokens[1] - .connect(traders[1]) - .approve(vault.address, ethers.constants.MaxUint256); - await vault.connect(traders[1]).manageUserBalance([ - { - kind: UserBalanceOpKind.DEPOSIT_INTERNAL, - asset: tokens[1].address, - amount: ethers.utils.parseEther("300.3"), - sender: traders[1].address, - recipient: traders[1].address, - }, - ]); - await vault - .connect(traders[1]) - .setRelayerApproval(traders[1].address, vaultRelayer.address, true); - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - partiallyFillable: false, - buyToken: tokens[0].address, - sellToken: tokens[1].address, - buyAmount: ethers.utils.parseEther("0.5"), - sellAmount: ethers.utils.parseEther("300.0"), - feeAmount: ethers.utils.parseEther("0.3"), - validTo: 0xffffffff, - appData: 2, - sellTokenBalance: OrderBalance.INTERNAL, - }, - traders[1], - SigningScheme.EIP712, - ); - - await tokens[0].mint(traders[2].address, ethers.utils.parseEther("2.002")); - await tokens[0] - .connect(traders[2]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: tokens[0].address, - buyToken: tokens[1].address, - sellAmount: ethers.utils.parseEther("2.0"), - buyAmount: ethers.utils.parseEther("1000.0"), - feeAmount: ethers.utils.parseEther("0.002"), - validTo: 0xffffffff, - appData: 2, - buyTokenBalance: OrderBalance.INTERNAL, - }, - traders[2], - SigningScheme.EIP712, - ); - - await tokens[1].mint(traders[3].address, ethers.utils.parseEther("1501.5")); - await tokens[1] - .connect(traders[3]) - .approve(vault.address, ethers.constants.MaxUint256); - await vault.connect(traders[3]).manageUserBalance([ - { - kind: UserBalanceOpKind.DEPOSIT_INTERNAL, - asset: tokens[1].address, - amount: ethers.utils.parseEther("1501.5"), - sender: traders[3].address, - recipient: traders[3].address, - }, - ]); - await vault - .connect(traders[3]) - .setRelayerApproval(traders[3].address, vaultRelayer.address, true); - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - partiallyFillable: false, - buyToken: tokens[0].address, - sellToken: tokens[1].address, - buyAmount: ethers.utils.parseEther("2.5"), - sellAmount: ethers.utils.parseEther("1500.0"), - feeAmount: ethers.utils.parseEther("1.5"), - validTo: 0xffffffff, - appData: 2, - sellTokenBalance: OrderBalance.INTERNAL, - buyTokenBalance: OrderBalance.INTERNAL, - }, - traders[3], - SigningScheme.EIP712, - ); - - await settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [tokens[0].address]: 550, - [tokens[1].address]: 1, - }), - ); - - expect(await tokens[1].balanceOf(traders[0].address)).to.equal( - ethers.utils.parseEther("550.0"), - ); - expect(await tokens[0].balanceOf(traders[1].address)).to.equal( - ethers.utils.parseEther("0.5"), - ); - expect( - await vault.getInternalBalance(traders[2].address, [tokens[1].address]), - ).to.deep.equal([ethers.utils.parseEther("1100")]); - expect( - await vault.getInternalBalance(traders[3].address, [tokens[0].address]), - ).to.deep.equal([ethers.utils.parseEther("2.5")]); - - expect(await tokens[0].balanceOf(settlement.address)).to.equal( - ethers.utils.parseEther("0.003"), - ); - expect(await tokens[1].balanceOf(settlement.address)).to.equal( - ethers.utils.parseEther("1.8"), - ); - }); -}); diff --git a/test/e2e/nonStandardErc20.test.ts b/test/e2e/nonStandardErc20.test.ts deleted file mode 100644 index 3b638c19..00000000 --- a/test/e2e/nonStandardErc20.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { expect } from "chai"; -import { Contract, Wallet } from "ethers"; -import { ethers } from "hardhat"; - -import { - OrderKind, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, -} from "../../src/ts"; - -import { deployTestContracts } from "./fixture"; - -describe("E2E: Non-Standard ERC20 Tokens", () => { - let solver: Wallet; - let traders: Wallet[]; - - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - let tokens: [Contract, Contract]; - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - settlement, - vaultRelayer, - wallets: [solver, ...traders], - } = deployment); - - const { authenticator, manager } = deployment; - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - - const ERC20NoReturn = await ethers.getContractFactory("ERC20NoReturn"); - const ERC20ReturningUint = await ethers.getContractFactory( - "ERC20ReturningUint", - ); - tokens = [await ERC20NoReturn.deploy(), await ERC20ReturningUint.deploy()]; - }); - - it("should allow trading non-standard ERC20 tokens", async () => { - // Just trade 1:1 - - const encoder = new SettlementEncoder(domainSeparator); - const amount = ethers.utils.parseEther("1.0"); - const feeAmount = ethers.utils.parseEther("0.01"); - - await tokens[0].mint(traders[0].address, amount.add(feeAmount)); - await tokens[0] - .connect(traders[0]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: tokens[0].address, - buyToken: tokens[1].address, - sellAmount: amount, - buyAmount: amount, - feeAmount, - validTo: 0xffffffff, - appData: 1, - }, - traders[0], - SigningScheme.EIP712, - ); - - await tokens[1].mint(traders[1].address, amount.add(feeAmount)); - await tokens[1] - .connect(traders[1]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - partiallyFillable: false, - sellToken: tokens[1].address, - buyToken: tokens[0].address, - buyAmount: amount, - sellAmount: amount, - feeAmount, - validTo: 0xffffffff, - appData: 2, - }, - traders[1], - SigningScheme.EIP712, - ); - - await settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [tokens[0].address]: 1, - [tokens[1].address]: 1, - }), - ); - - expect(await tokens[0].balanceOf(settlement.address)).to.equal(feeAmount); - expect(await tokens[0].balanceOf(traders[1].address)).to.equal(amount); - - expect(await tokens[1].balanceOf(settlement.address)).to.equal(feeAmount); - expect(await tokens[1].balanceOf(traders[0].address)).to.equal(amount); - }); -}); diff --git a/test/e2e/offchainAllowances.test.ts b/test/e2e/offchainAllowances.test.ts deleted file mode 100644 index 56629d41..00000000 --- a/test/e2e/offchainAllowances.test.ts +++ /dev/null @@ -1,285 +0,0 @@ -import { expect } from "chai"; -import { Contract, Wallet } from "ethers"; -import { ethers } from "hardhat"; - -import { - InteractionStage, - OrderBalance, - OrderKind, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, - grantRequiredRoles, -} from "../../src/ts"; -import { UserBalanceOpKind } from "../balancer"; - -import { deployTestContracts } from "./fixture"; - -describe("E2E: Off-chain Allowances", () => { - let manager: Wallet; - let solver: Wallet; - let traders: Wallet[]; - - let vault: Contract; - let vaultAuthorizer: Contract; - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - let eurs: [Contract, Contract]; - const ONE_EUR = ethers.utils.parseEther("1.0"); - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - vault, - vaultAuthorizer, - settlement, - vaultRelayer, - manager, - wallets: [solver, ...traders], - } = deployment); - - const { authenticator } = deployment; - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - - const ERC20 = await ethers.getContractFactory("ERC20PresetPermit"); - eurs = [await ERC20.deploy("EUR1"), await ERC20.deploy("EUR2")]; - }); - - describe("EIP-2612 Permit", () => { - it("permits trader allowance with settlement", async () => { - // Settle a trivial trade where all € stable coins trade 1:1. - - const encoder = new SettlementEncoder(domainSeparator); - - await eurs[0].mint(traders[0].address, ONE_EUR); - await eurs[0] - .connect(traders[0]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: eurs[0].address, - buyToken: eurs[1].address, - sellAmount: ONE_EUR, - buyAmount: ONE_EUR, - feeAmount: ethers.constants.Zero, - validTo: 0xffffffff, - appData: 1, - }, - traders[0], - SigningScheme.EIP712, - ); - - await eurs[1].mint(traders[1].address, ONE_EUR); - - const permit = { - owner: traders[1].address, - spender: vaultRelayer.address, - value: ONE_EUR, - nonce: await eurs[1].nonces(traders[1].address), - deadline: 0xffffffff, - }; - const { r, s, v } = ethers.utils.splitSignature( - await traders[1]._signTypedData( - { - name: await eurs[1].name(), - version: "1", - chainId: domainSeparator.chainId, - verifyingContract: eurs[1].address, - }, - { - Permit: [ - { name: "owner", type: "address" }, - { name: "spender", type: "address" }, - { name: "value", type: "uint256" }, - { name: "nonce", type: "uint256" }, - { name: "deadline", type: "uint256" }, - ], - }, - permit, - ), - ); - encoder.encodeInteraction( - { - target: eurs[1].address, - callData: eurs[1].interface.encodeFunctionData("permit", [ - permit.owner, - permit.spender, - permit.value, - permit.deadline, - v, - r, - s, - ]), - }, - InteractionStage.PRE, - ); - - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - partiallyFillable: false, - buyToken: eurs[0].address, - sellToken: eurs[1].address, - buyAmount: ONE_EUR, - sellAmount: ONE_EUR, - feeAmount: ethers.constants.Zero, - validTo: 0xffffffff, - appData: 2, - }, - traders[1], - SigningScheme.EIP712, - ); - - await settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [eurs[0].address]: 1, - [eurs[1].address]: 1, - }), - ); - - expect(await eurs[1].balanceOf(traders[1].address)).to.deep.equal( - ethers.constants.Zero, - ); - }); - }); - - describe("Vault Allowance", () => { - it("allows setting Vault relayer approval with interactions", async () => { - // Settle a trivial trade where all € stable coins trade 1:1. - - const encoder = new SettlementEncoder(domainSeparator); - - await eurs[0].mint(traders[0].address, ONE_EUR); - await eurs[0] - .connect(traders[0]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: eurs[0].address, - buyToken: eurs[1].address, - sellAmount: ONE_EUR, - buyAmount: ONE_EUR, - feeAmount: ethers.constants.Zero, - validTo: 0xffffffff, - appData: 1, - }, - traders[0], - SigningScheme.EIP712, - ); - - await eurs[1].mint(traders[1].address, ONE_EUR); - await eurs[1] - .connect(traders[1]) - .approve(vault.address, ethers.constants.MaxUint256); - await vault.connect(traders[1]).manageUserBalance([ - { - kind: UserBalanceOpKind.DEPOSIT_INTERNAL, - asset: eurs[1].address, - amount: ONE_EUR, - sender: traders[1].address, - recipient: traders[1].address, - }, - ]); - - // The settlement contract needs to be authorized as a relayer to change - // relayer allowances for users by signature. - await vaultAuthorizer - .connect(manager) - .grantRole( - ethers.utils.solidityKeccak256( - ["uint256", "bytes4"], - [vault.address, vault.interface.getSighash("setRelayerApproval")], - ), - settlement.address, - ); - await grantRequiredRoles( - vaultAuthorizer.connect(manager), - vault.address, - vaultRelayer.address, - ); - - const deadline = 0xffffffff; - const { chainId } = await ethers.provider.getNetwork(); - const approval = vault.interface.encodeFunctionData( - "setRelayerApproval", - [traders[1].address, vaultRelayer.address, true], - ); - const { v, r, s } = ethers.utils.splitSignature( - await traders[1]._signTypedData( - { - name: "Balancer V2 Vault", - version: "1", - chainId, - verifyingContract: vault.address, - }, - { - SetRelayerApproval: [ - { name: "calldata", type: "bytes" }, - { name: "sender", type: "address" }, - { name: "nonce", type: "uint256" }, - { name: "deadline", type: "uint256" }, - ], - }, - { - calldata: approval, - sender: settlement.address, - nonce: 0, - deadline, - }, - ), - ); - encoder.encodeInteraction( - { - target: vault.address, - callData: ethers.utils.hexConcat([ - approval, - ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint8", "bytes32", "bytes32"], - [deadline, v, r, s], - ), - ]), - }, - InteractionStage.PRE, - ); - - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - partiallyFillable: false, - buyToken: eurs[0].address, - sellToken: eurs[1].address, - buyAmount: ONE_EUR, - sellAmount: ONE_EUR, - feeAmount: ethers.constants.Zero, - validTo: 0xffffffff, - appData: 2, - sellTokenBalance: OrderBalance.INTERNAL, - }, - traders[1], - SigningScheme.EIP712, - ); - - await settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [eurs[0].address]: 1, - [eurs[1].address]: 1, - }), - ); - - expect(await eurs[1].balanceOf(traders[1].address)).to.deep.equal( - ethers.constants.Zero, - ); - }); - }); -}); diff --git a/test/e2e/smartOrder.test.ts b/test/e2e/smartOrder.test.ts deleted file mode 100644 index 921cfd3f..00000000 --- a/test/e2e/smartOrder.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json"; -import { expect } from "chai"; -import { Contract, ContractFactory, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import { - OrderBalance, - OrderKind, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, -} from "../../src/ts"; -import { decodeOrder } from "../encoding"; - -import { deployTestContracts } from "./fixture"; - -describe("E2E: Dumb Smart Order", () => { - let deployer: Wallet; - let solver: Wallet; - let traders: Wallet[]; - - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - let tokens: [Contract, Contract]; - - let SmartSellOrder: ContractFactory; - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - deployer, - settlement, - vaultRelayer, - wallets: [solver, ...traders], - } = deployment); - - const { authenticator, manager } = deployment; - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - - tokens = [ - await waffle.deployContract(deployer, ERC20, ["T0", 18]), - await waffle.deployContract(deployer, ERC20, ["T1", 18]), - ]; - - SmartSellOrder = await ethers.getContractFactory("SmartSellOrder"); - }); - - it("permits trader allowance with settlement", async () => { - // Settle half of the smart order. - const encoder = new SettlementEncoder(domainSeparator); - - await tokens[0].mint(traders[0].address, ethers.utils.parseEther("1.01")); - await tokens[0] - .connect(traders[0]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - partiallyFillable: false, - sellToken: tokens[0].address, - buyToken: tokens[1].address, - sellAmount: ethers.utils.parseEther("1.0"), - buyAmount: ethers.utils.parseEther("0.5"), - feeAmount: ethers.utils.parseEther("0.01"), - validTo: 0xffffffff, - appData: 1, - }, - traders[0], - SigningScheme.EIP712, - ); - - const smartOrder = await SmartSellOrder.connect(traders[1]).deploy( - settlement.address, - tokens[1].address, - tokens[0].address, - 0xffffffff, - ethers.utils.parseEther("1.0"), - ethers.utils.parseEther("0.1"), - ); - await tokens[1].mint(traders[1].address, ethers.utils.parseEther("1.1")); - await tokens[1] - .connect(traders[1]) - .transfer(smartOrder.address, ethers.utils.parseEther("1.1")); - - const smartOrderSellAmount = ethers.utils.parseEther("0.5"); - const smartOrderTrade = decodeOrder( - await smartOrder.orderForSellAmount(smartOrderSellAmount), - ); - expect(smartOrderTrade).to.deep.equal({ - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: tokens[1].address, - buyToken: tokens[0].address, - receiver: traders[1].address, - sellAmount: smartOrderSellAmount, - buyAmount: ethers.utils.parseEther("0.75"), - feeAmount: ethers.utils.parseEther("0.05"), - validTo: 0xffffffff, - appData: await smartOrder.APPDATA(), - sellTokenBalance: OrderBalance.ERC20, - buyTokenBalance: OrderBalance.ERC20, - }); - - await encoder.encodeTrade(smartOrderTrade, { - scheme: SigningScheme.EIP1271, - data: { - verifier: smartOrder.address, - signature: ethers.utils.defaultAbiCoder.encode( - ["uint256"], - [smartOrderSellAmount], - ), - }, - }); - - await settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [tokens[0].address]: 10, - [tokens[1].address]: 15, - }), - ); - - expect(await tokens[0].balanceOf(traders[1].address)).to.deep.equal( - ethers.utils.parseEther("0.75"), - ); - }); -}); diff --git a/test/e2e/uniswapTrade.test.ts b/test/e2e/uniswapTrade.test.ts deleted file mode 100644 index e7080af8..00000000 --- a/test/e2e/uniswapTrade.test.ts +++ /dev/null @@ -1,206 +0,0 @@ -import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json"; -import UniswapV2Factory from "@uniswap/v2-core/build/UniswapV2Factory.json"; -import UniswapV2Pair from "@uniswap/v2-core/build/UniswapV2Pair.json"; -import { expect } from "chai"; -import { Contract, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import { - OrderKind, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, -} from "../../src/ts"; - -import { deployTestContracts } from "./fixture"; - -describe("E2E: Should Trade Surplus With Uniswap", () => { - let deployer: Wallet; - let solver: Wallet; - let pooler: Wallet; - let traders: Wallet[]; - - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - let weth: Contract; - let usdt: Contract; - let uniswapPair: Contract; - let isWethToken0: boolean; - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - deployer, - settlement, - vaultRelayer, - wallets: [solver, pooler, ...traders], - } = deployment); - - const { authenticator, manager } = deployment; - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - - weth = await waffle.deployContract(deployer, ERC20, ["WETH", 18]); - usdt = await waffle.deployContract(deployer, ERC20, ["USDT", 6]); - - const uniswapFactory = await waffle.deployContract( - deployer, - UniswapV2Factory, - [deployer.address], - ); - await uniswapFactory.createPair(weth.address, usdt.address); - uniswapPair = new Contract( - await uniswapFactory.getPair(weth.address, usdt.address), - UniswapV2Pair.abi, - deployer, - ); - - // NOTE: Which token ends up as token 0 or token 1 depends on the addresses - // of the WETH and USDT token which can change depending on which order the - // tests are run. Because of this, check the Uniswap pair to see which token - // ended up on which index. - isWethToken0 = (await uniswapPair.token0()) === weth.address; - }); - - it("should settle two overlapping orders and trade surplus with Uniswap", async () => { - // Settles the following batch: - // - // /----(1. SELL 1 WETH for USDT if p(WETH) >= 500)-----\ - // | | - // | v - // [USDT]<---(Uniswap Pair 1000 WETH / 600.000 USDT)--->[WETH] - // ^ | - // | | - // \----(2. BUY 0.5 WETH for USDT if p(WETH) <= 600)----/ - - const uniswapWethReserve = ethers.utils.parseEther("1000.0"); - const uniswapUsdtReserve = ethers.utils.parseUnits("600000.0", 6); - await weth.mint(uniswapPair.address, uniswapWethReserve); - await usdt.mint(uniswapPair.address, uniswapUsdtReserve); - await uniswapPair.mint(pooler.address); - - // The current batch has a sell order selling 1 WETH and a buy order buying - // 0.5 WETH. This means there is exactly a surplus 0.5 WETH that needs to be - // sold to Uniswap. Uniswap is governed by a balancing equation which can be - // used to compute the exact buy amount for selling the 0.5 WETH and we can - // use to build our the settlement with a smart contract interaction. - // ``` - // (reserveWETH + inWETH * 0.997) * (reserveUSDT - outUSDT) = reserveWETH * reserveUSDT - // outUSDT = (reserveUSDT * inWETH * 0.997) / (reserveWETH + inWETH * 0.997) - // = (reserveUSDT * inWETH * 997) / (reserveWETH * 1000 + inWETH * 997) - // ``` - const uniswapWethInAmount = ethers.utils.parseEther("0.5"); - const uniswapUsdtOutAmount = uniswapUsdtReserve - .mul(uniswapWethInAmount) - .mul(997) - .div(uniswapWethReserve.mul(1000).add(uniswapWethInAmount.mul(997))); - - const encoder = new SettlementEncoder(domainSeparator); - - await weth.mint(traders[0].address, ethers.utils.parseEther("1.001")); - await weth - .connect(traders[0]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: weth.address, - buyToken: usdt.address, - sellAmount: ethers.utils.parseEther("1.0"), - buyAmount: ethers.utils.parseUnits("500.0", 6), - feeAmount: ethers.utils.parseEther("0.001"), - validTo: 0xffffffff, - appData: 1, - }, - traders[0], - SigningScheme.EIP712, - ); - - await usdt.mint(traders[1].address, ethers.utils.parseUnits("300.3", 6)); - await usdt - .connect(traders[1]) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - await encoder.signEncodeTrade( - { - kind: OrderKind.BUY, - partiallyFillable: false, - buyToken: weth.address, - sellToken: usdt.address, - buyAmount: ethers.utils.parseEther("0.5"), - sellAmount: ethers.utils.parseUnits("300.0", 6), - feeAmount: ethers.utils.parseUnits("0.3", 6), - validTo: 0xffffffff, - appData: 2, - }, - traders[1], - SigningScheme.EIP712, - ); - - encoder.encodeInteraction({ - target: weth.address, - callData: weth.interface.encodeFunctionData("transfer", [ - uniswapPair.address, - uniswapWethInAmount, - ]), - }); - - const [amount0Out, amount1Out] = isWethToken0 - ? [0, uniswapUsdtOutAmount] - : [uniswapUsdtOutAmount, 0]; - encoder.encodeInteraction({ - target: uniswapPair.address, - callData: uniswapPair.interface.encodeFunctionData("swap", [ - amount0Out, - amount1Out, - settlement.address, - "0x", - ]), - }); - - await settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [weth.address]: uniswapUsdtOutAmount, - [usdt.address]: uniswapWethInAmount, - }), - ); - - expect(await weth.balanceOf(settlement.address)).to.deep.equal( - ethers.utils.parseEther("0.001"), - ); - expect(await usdt.balanceOf(settlement.address)).to.deep.equal( - ethers.utils.parseUnits("0.3", 6), - ); - - expect(await weth.balanceOf(traders[0].address)).to.deep.equal( - ethers.constants.Zero, - ); - expect(await usdt.balanceOf(traders[0].address)).to.deep.equal( - uniswapUsdtOutAmount.mul(2), - ); - - expect(await weth.balanceOf(traders[1].address)).to.deep.equal( - ethers.utils.parseEther("0.5"), - ); - expect(await usdt.balanceOf(traders[1].address)).to.deep.equal( - ethers.utils - .parseUnits("300.3", 6) - .sub(uniswapUsdtOutAmount.add(ethers.utils.parseUnits("0.3", 6))), - ); - - const [token0Reserve, token1Reserve] = await uniswapPair.getReserves(); - const [finalWethReserve, finalUsdtReserve] = isWethToken0 - ? [token0Reserve, token1Reserve] - : [token1Reserve, token0Reserve]; - expect([finalWethReserve, finalUsdtReserve]).to.deep.equal([ - uniswapWethReserve.add(uniswapWethInAmount), - uniswapUsdtReserve.sub(uniswapUsdtOutAmount), - ]); - }); -}); diff --git a/test/e2e/upgradeAuthenticator.test.ts b/test/e2e/upgradeAuthenticator.test.ts deleted file mode 100644 index abfa0bd2..00000000 --- a/test/e2e/upgradeAuthenticator.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { expect } from "chai"; -import { Contract, Wallet } from "ethers"; -import { deployments, ethers } from "hardhat"; - -import { proxyInterface } from "../../src/ts"; - -import { deployTestContracts } from "./fixture"; - -async function rejectError( - promise: Promise, -): Promise { - try { - await promise; - return undefined; - } catch (err) { - if (err instanceof Error) { - return err; - } else { - throw new Error("Invalid rejection output"); - } - } -} - -async function upgrade( - proxyOwner: Wallet, - contractName: string, - newContractName: string, -) { - // Note that deterministic deployment and gasLimit are not needed/used here as deployment args. - await deployments.deploy(contractName, { - contract: newContractName, - // From differs from initial deployment here since the proxy owner is the Authenticator manager. - from: proxyOwner.address, - proxy: true, - }); -} - -describe("E2E: Upgrade Authenticator", () => { - let authenticator: Contract; - let deployer: Wallet; - let owner: Wallet; - let manager: Wallet; - let nobody: Wallet; - let newOwner: Wallet; - let newManager: Wallet; - let solver: Wallet; - - beforeEach(async () => { - ({ - authenticator, - deployer, - owner, - manager, - wallets: [nobody, newOwner, newManager, solver], - } = await deployTestContracts()); - }); - - it("should upgrade authenticator", async () => { - const GPv2AllowListAuthenticationV2 = await ethers.getContractFactory( - "GPv2AllowListAuthenticationV2", - deployer, - ); - // Note that, before the upgrade this is actually the old instance - const authenticatorV2 = GPv2AllowListAuthenticationV2.attach( - authenticator.address, - ); - // This method doesn't exist before upgrade - await expect(authenticatorV2.newMethod()).to.be.reverted; - - await upgrade( - owner, - "GPv2AllowListAuthentication", - "GPv2AllowListAuthenticationV2", - ); - // This method should exist on after upgrade - expect(await authenticatorV2.newMethod()).to.equal(1337); - }); - - it("should preserve storage", async () => { - await authenticator.connect(manager).addSolver(solver.address); - await authenticator.connect(manager).setManager(newManager.address); - - // Upgrade after storage is set with **proxy owner**; - await upgrade( - owner, - "GPv2AllowListAuthentication", - "GPv2AllowListAuthenticationV2", - ); - - const GPv2AllowListAuthenticationV2 = await ethers.getContractFactory( - "GPv2AllowListAuthenticationV2", - deployer, - ); - const authenticatorV2 = GPv2AllowListAuthenticationV2.attach( - authenticator.address, - ); - - // Both, the listed solvers and updated manager are still set - expect(await authenticatorV2.isSolver(solver.address)).to.equal(true); - expect(await authenticatorV2.manager()).to.equal(newManager.address); - }); - - it("should allow the proxy owner to change the manager", async () => { - await authenticator.connect(owner).setManager(newManager.address); - expect(await authenticator.manager()).to.equal(newManager.address); - }); - - it("should be able to transfer proxy ownership", async () => { - const proxy = proxyInterface(authenticator); - await proxy.connect(owner).transferOwnership(newOwner.address); - expect(await proxy.owner()).to.equal(newOwner.address); - - await upgrade( - newOwner, - "GPv2AllowListAuthentication", - "GPv2AllowListAuthenticationV2", - ); - }); - - it("should revert when not upgrading with the authentication manager", async () => { - await authenticator.connect(owner).setManager(newManager.address); - expect( - await rejectError( - upgrade( - newManager, - "GPv2AllowListAuthentication", - "GPv2AllowListAuthenticationV2", - ), - ), - ).to.not.be.undefined; - }); - - it("should revert when not upgrading with the proxy owner", async () => { - expect( - await rejectError( - upgrade( - nobody, - "GPv2AllowListAuthentication", - "GPv2AllowListAuthenticationV2", - ), - ), - ).to.not.be.undefined; - }); -}); diff --git a/test/e2e/wineOilMarket.test.ts b/test/e2e/wineOilMarket.test.ts deleted file mode 100644 index 3531903a..00000000 --- a/test/e2e/wineOilMarket.test.ts +++ /dev/null @@ -1,198 +0,0 @@ -import ERC20 from "@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json"; -import { expect } from "chai"; -import { BigNumber, BigNumberish, Contract, Wallet } from "ethers"; -import { ethers, waffle } from "hardhat"; - -import { - Order, - OrderKind, - SettlementEncoder, - SigningScheme, - TypedDataDomain, - domain, -} from "../../src/ts"; - -import { deployTestContracts } from "./fixture"; - -function ceilDiv(p: BigNumberish, q: BigNumberish): BigNumber { - return BigNumber.from(p).add(q).sub(1).div(q); -} - -describe("E2E: RetrETH Red Wine and Olive Oil Market", () => { - let deployer: Wallet; - let solver: Wallet; - let traders: Wallet[]; - - let settlement: Contract; - let vaultRelayer: Contract; - let domainSeparator: TypedDataDomain; - - beforeEach(async () => { - const deployment = await deployTestContracts(); - - ({ - deployer, - settlement, - vaultRelayer, - wallets: [solver, ...traders], - } = deployment); - - const { authenticator, manager } = deployment; - await authenticator.connect(manager).addSolver(solver.address); - - const { chainId } = await ethers.provider.getNetwork(); - domainSeparator = domain(chainId, settlement.address); - }); - - it("should settle red wine and olive oil market", async () => { - // Settlement for the RetrETH wine and olive oil market: - // - // /---(6. BUY 10 🍷 with 💶 if p(🍷) <= 13)--> [🍷] - // | | - // | | - // [💶] (1. SELL 12 🍷 for 🫒 if p(🍷) >= p(🫒)) - // |^ | - // || | - // |\--(4. SELL 15 🫒 for 💶 if p(🫒) >= 12)--\ v - // \---(5. BUY 4 🫒 with 💶 if p(🫒) <= 13)---> [🫒] - - const STARTING_BALANCE = ethers.utils.parseEther("1000.0"); - const erc20 = (symbol: string) => - waffle.deployContract(deployer, ERC20, [symbol, 18]); - - const eur = await erc20("💶"); - const oil = await erc20("🫒"); - const wine = await erc20("🍷"); - - const orderDefaults = { - validTo: 0xffffffff, - feeAmount: ethers.utils.parseEther("1.0"), - }; - const encoder = new SettlementEncoder(domainSeparator); - - const addOrder = async ( - trader: Wallet, - order: Order, - executedAmount?: BigNumber, - ) => { - const sellToken = await ethers.getContractAt( - ERC20.abi, - order.sellToken, - deployer, - ); - await sellToken.mint(trader.address, STARTING_BALANCE); - await sellToken - .connect(trader) - .approve(vaultRelayer.address, ethers.constants.MaxUint256); - - await encoder.signEncodeTrade(order, trader, SigningScheme.EIP712, { - executedAmount, - }); - }; - - await addOrder(traders[0], { - ...orderDefaults, - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: wine.address, - buyToken: oil.address, - sellAmount: ethers.utils.parseEther("12.0"), - buyAmount: ethers.utils.parseEther("12.0"), - appData: 1, - }); - - await addOrder(traders[1], { - ...orderDefaults, - kind: OrderKind.SELL, - partiallyFillable: false, - sellToken: oil.address, - buyToken: eur.address, - sellAmount: ethers.utils.parseEther("15.0"), - buyAmount: ethers.utils.parseEther("180.0"), - appData: 4, - }); - - await addOrder( - traders[2], - { - ...orderDefaults, - kind: OrderKind.BUY, - partiallyFillable: true, - buyToken: oil.address, - sellToken: eur.address, - buyAmount: ethers.utils.parseEther("4.0"), - sellAmount: ethers.utils.parseEther("52.0"), - appData: 5, - }, - ethers.utils.parseEther("27.0").div(13), - ); - - await addOrder( - traders[3], - { - ...orderDefaults, - kind: OrderKind.BUY, - partiallyFillable: true, - buyToken: wine.address, - sellToken: eur.address, - buyAmount: ethers.utils.parseEther("20.0"), - sellAmount: ethers.utils.parseEther("280.0"), - appData: 6, - }, - ethers.utils.parseEther("12.0"), - ); - - await settlement.connect(solver).settle( - ...encoder.encodedSettlement({ - [eur.address]: ethers.utils.parseEther("1.0"), - [oil.address]: ethers.utils.parseEther("13.0"), - [wine.address]: ethers.utils.parseEther("14.0"), - }), - ); - - expect(await wine.balanceOf(traders[0].address)).to.deep.equal( - STARTING_BALANCE.sub(ethers.utils.parseEther("12.0")).sub( - orderDefaults.feeAmount, - ), - ); - expect(await oil.balanceOf(traders[0].address)).to.deep.equal( - ceilDiv(ethers.utils.parseEther("12.0").mul(14), 13), - ); - - expect(await oil.balanceOf(traders[1].address)).to.deep.equal( - STARTING_BALANCE.sub(ethers.utils.parseEther("15.0")).sub( - orderDefaults.feeAmount, - ), - ); - expect(await eur.balanceOf(traders[1].address)).to.deep.equal( - ethers.utils.parseEther("15.0").mul(13), - ); - - expect(await eur.balanceOf(traders[2].address)).to.deep.equal( - STARTING_BALANCE.sub(ethers.utils.parseEther("27.0")) - .sub( - orderDefaults.feeAmount - .mul(ethers.utils.parseEther("27.0").div(13)) - .div(ethers.utils.parseEther("4.0")), - ) - // NOTE: Account for rounding error from computing sell amount that is - // an order of magnitude larger than executed buy amount from the - // settlement. - .add(1), - ); - expect(await oil.balanceOf(traders[2].address)).to.deep.equal( - ethers.utils.parseEther("27.0").div(13), - ); - - expect(await eur.balanceOf(traders[3].address)).to.deep.equal( - STARTING_BALANCE.sub(ethers.utils.parseEther("12.0").mul(14)).sub( - orderDefaults.feeAmount - .mul(ethers.utils.parseEther("12.0")) - .div(ethers.utils.parseEther("20.0")), - ), - ); - expect(await wine.balanceOf(traders[3].address)).to.deep.equal( - ethers.utils.parseEther("12.0"), - ); - }); -}); 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/test/encoding.ts b/test/encoding.ts deleted file mode 100644 index 4e2a0b4f..00000000 --- a/test/encoding.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { BigNumber } from "ethers"; -import { ethers } from "hardhat"; - -import { Order, OrderBalance, OrderKind } from "../src/ts"; - -type AbiOrder = [ - string, - string, - string, - BigNumber, - BigNumber, - number, - string, - BigNumber, - string, - boolean, - string, - string, -]; - -function decodeEnum(hash: string, values: T[]): T { - for (const value of values) { - if (hash == ethers.utils.id(`${value}`)) { - return value; - } - } - throw new Error(`invalid enum hash '${hash}'`); -} - -export function decodeOrderKind(kindHash: string): OrderKind { - return decodeEnum(kindHash, [OrderKind.SELL, OrderKind.BUY]); -} - -export function decodeOrderBalance(balanceHash: string): OrderBalance { - return decodeEnum(balanceHash, [ - OrderBalance.ERC20, - OrderBalance.EXTERNAL, - OrderBalance.INTERNAL, - ]); -} - -export function decodeOrder(order: AbiOrder): Order { - return { - sellToken: order[0], - buyToken: order[1], - receiver: order[2], - sellAmount: order[3], - buyAmount: order[4], - validTo: order[5], - appData: order[6], - feeAmount: order[7], - kind: decodeOrderKind(order[8]), - partiallyFillable: order[9], - sellTokenBalance: decodeOrderBalance(order[10]), - buyTokenBalance: decodeOrderBalance(order[11]), - }; -} diff --git a/test/libraries/encoders/SwapEncoder.sol b/test/libraries/encoders/SwapEncoder.sol index e8ad513c..e51771b6 100644 --- a/test/libraries/encoders/SwapEncoder.sol +++ b/test/libraries/encoders/SwapEncoder.sol @@ -124,8 +124,8 @@ library SwapEncoder { function toSwapStep(State storage state, Swap memory swap) private returns (IVault.BatchSwapStep memory step) { TokenRegistry.State storage tokenRegistry = state.tokenRegistry.tokenRegistry(); step.poolId = swap.poolId; - step.assetInIndex = tokenRegistry.pushIfNotPresentIndexOf(swap.assetIn); - step.assetOutIndex = tokenRegistry.pushIfNotPresentIndexOf(swap.assetOut); + step.assetInIndex = TokenRegistry.tokenIndex(tokenRegistry, swap.assetIn); + step.assetOutIndex = TokenRegistry.tokenIndex(tokenRegistry, swap.assetOut); step.amount = swap.amount; step.userData = swap.userData; } diff --git a/test/libraries/encoders/TokenRegistry.sol b/test/libraries/encoders/TokenRegistry.sol index d0f710e5..1d6d29d8 100644 --- a/test/libraries/encoders/TokenRegistry.sol +++ b/test/libraries/encoders/TokenRegistry.sol @@ -104,4 +104,10 @@ library TokenRegistry { uint256 buyTokenIndex = pushIfNotPresentIndexOf(state, order.buyToken); return (sellTokenIndex - 1, buyTokenIndex - 1); } + + /// @dev Returns the token index for the specified token + function tokenIndex(State storage state, IERC20 token) internal hydrateArray(state) returns (uint256) { + uint256 index = pushIfNotPresentIndexOf(state, token); + return index - 1; + } } diff --git a/test/src/EIP173Proxy.sol b/test/src/EIP173Proxy.sol new file mode 100644 index 00000000..b4a71ee4 --- /dev/null +++ b/test/src/EIP173Proxy.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// solhint-disable-next-line compiler-version +pragma solidity ^0.7; + +import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; + +interface IERC165 { + function supportsInterface(bytes4 id) external view returns (bool); +} + +// ref: https://github.com/wighawag/hardhat-deploy/blob/e0ffcf9e7dc92b246e832c4d175f9dbd8b6df14d/solc_0.8/proxy/EIP173Proxy.sol +contract EIP173Proxy is Proxy { + bytes32 constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + bytes32 constant OWNER_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + modifier onlyOwner() { + require(msg.sender == _owner(), "NOT_AUTHORIZED"); + _; + } + + constructor(address implAddress, address ownerAddress, bytes memory data) { + _setOwner(ownerAddress); + _setImplementation(implAddress, data); + } + + function owner() external view returns (address) { + return _owner(); + } + + function supportsInterface(bytes4 id) external view returns (bool) { + if (id == 0x01ffc9a7 || id == 0x7f5828d0) { + return true; + } + if (id == 0xFFFFFFFF) { + return false; + } + + IERC165 implementation; + assembly { + implementation := sload(IMPLEMENTATION_SLOT) + } + + // Technically this is not standard compliant as ERC-165 require 30,000 gas which that call cannot ensure + // because it is itself inside `supportsInterface` that might only get 30,000 gas. + // In practise this is unlikely to be an issue. + try implementation.supportsInterface(id) returns (bool support) { + return support; + } catch { + return false; + } + } + + function transferOwnership(address newOwner) external onlyOwner { + _setOwner(newOwner); + } + + function upgradeTo(address newImplementation) external onlyOwner { + _setImplementation(newImplementation, ""); + } + + function upgradeToAndCall(address newImplementation, bytes calldata data) external payable onlyOwner { + _setImplementation(newImplementation, data); + } + + function _owner() internal view returns (address adminAddress) { + assembly { + adminAddress := sload(OWNER_SLOT) + } + } + + function _setOwner(address newOwner) internal { + address previousOwner = _owner(); + assembly { + sstore(OWNER_SLOT, newOwner) + } + emit OwnershipTransferred(previousOwner, newOwner); + } + + function _implementation() internal view override returns (address) { + address impl; + assembly { + impl := sload(IMPLEMENTATION_SLOT) + } + return impl; + } + + function _setImplementation(address impl, bytes memory data) internal { + assembly { + sstore(IMPLEMENTATION_SLOT, impl) + } + + if (data.length > 0) { + // solhint-disable-next-line avoid-low-level-calls + (bool success,) = impl.delegatecall(data); + if (!success) { + assembly { + // This assembly ensure the revert contains the exact string data + let returnDataSize := returndatasize() + returndatacopy(0, 0, returnDataSize) + revert(0, returnDataSize) + } + } + } + } +} diff --git a/test/src/ERC20Mintable.sol b/test/src/ERC20Mintable.sol new file mode 120000 index 00000000..c31a1693 --- /dev/null +++ b/test/src/ERC20Mintable.sol @@ -0,0 +1 @@ +../e2e/ERC20Mintable.sol \ No newline at end of file diff --git a/test/src/GPv2AllowListAuthenticationV2.sol b/test/src/GPv2AllowListAuthenticationV2.sol deleted file mode 100644 index 40dc6ca2..00000000 --- a/test/src/GPv2AllowListAuthenticationV2.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// solhint-disable-next-line compiler-version -pragma solidity >=0.7.6 <0.9.0; - -import "src/contracts/GPv2AllowListAuthentication.sol"; - -contract GPv2AllowListAuthenticationV2 is GPv2AllowListAuthentication { - function newMethod() external pure returns (uint256) { - return 1337; - } -} diff --git a/test/src/NonStandardERC20.sol b/test/src/NonStandardERC20.sol deleted file mode 100644 index c91f8843..00000000 --- a/test/src/NonStandardERC20.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// solhint-disable-next-line compiler-version -pragma solidity >=0.7.6 <0.9.0; - -import "src/contracts/libraries/SafeMath.sol"; - -abstract contract NonStandardERC20 { - using SafeMath for uint256; - - mapping(address => uint256) public balanceOf; - mapping(address => mapping(address => uint256)) public allowance; - - function mint(address to, uint256 amount) external { - balanceOf[to] = balanceOf[to].add(amount); - } - - function approve(address spender, uint256 amount) external { - allowance[msg.sender][spender] = amount; - } - - function transfer_(address to, uint256 amount) internal { - balanceOf[msg.sender] = balanceOf[msg.sender].sub(amount); - balanceOf[to] = balanceOf[to].add(amount); - } - - function transferFrom_(address from, address to, uint256 amount) internal { - allowance[from][msg.sender] = allowance[from][msg.sender].sub(amount); - balanceOf[from] = balanceOf[from].sub(amount); - balanceOf[to] = balanceOf[to].add(amount); - } -} - -contract ERC20NoReturn is NonStandardERC20 { - function transfer(address to, uint256 amount) external { - transfer_(to, amount); - } - - function transferFrom(address from, address to, uint256 amount) external { - transferFrom_(from, to, amount); - } -} - -contract ERC20ReturningUint is NonStandardERC20 { - // Largest 256-bit prime :) - uint256 private constant OK = 115792089237316195423570985008687907853269984665640564039457584007913129639747; - - function transfer(address to, uint256 amount) external returns (uint256) { - transfer_(to, amount); - return OK; - } - - function transferFrom(address from, address to, uint256 amount) external returns (uint256) { - transferFrom_(from, to, amount); - return OK; - } -} diff --git a/test/src/SmartSellOrder.sol b/test/src/SmartSellOrder.sol deleted file mode 100644 index 23336f53..00000000 --- a/test/src/SmartSellOrder.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// solhint-disable-next-line compiler-version -pragma solidity >=0.7.6 <0.9.0; -pragma abicoder v2; - -import "src/contracts/GPv2Settlement.sol"; -import "src/contracts/interfaces/GPv2EIP1271.sol"; -import "src/contracts/interfaces/IERC20.sol"; -import "src/contracts/libraries/GPv2Order.sol"; -import "src/contracts/libraries/GPv2SafeERC20.sol"; -import "src/contracts/libraries/SafeMath.sol"; - -/// @title Proof of Concept Smart Order -/// @author Gnosis Developers -contract SmartSellOrder is EIP1271Verifier { - using GPv2Order for GPv2Order.Data; - using GPv2SafeERC20 for IERC20; - using SafeMath for uint256; - - bytes32 public constant APPDATA = keccak256("SmartSellOrder"); - - address public immutable owner; - bytes32 public immutable domainSeparator; - IERC20 public immutable sellToken; - IERC20 public immutable buyToken; - uint256 public immutable totalSellAmount; - uint256 public immutable totalFeeAmount; - uint32 public immutable validTo; - - constructor( - GPv2Settlement settlement, - IERC20 sellToken_, - IERC20 buyToken_, - uint32 validTo_, - uint256 totalSellAmount_, - uint256 totalFeeAmount_ - ) { - owner = msg.sender; - domainSeparator = settlement.domainSeparator(); - sellToken = sellToken_; - buyToken = buyToken_; - validTo = validTo_; - totalSellAmount = totalSellAmount_; - totalFeeAmount = totalFeeAmount_; - - sellToken_.approve(address(settlement.vaultRelayer()), type(uint256).max); - } - - modifier onlyOwner() { - require(msg.sender == owner, "not owner"); - _; - } - - function withdraw(uint256 amount) external onlyOwner { - sellToken.safeTransfer(owner, amount); - } - - function close() external onlyOwner { - uint256 balance = sellToken.balanceOf(address(this)); - if (balance != 0) { - sellToken.safeTransfer(owner, balance); - } - } - - function isValidSignature(bytes32 hash, bytes memory signature) - external - view - override - returns (bytes4 magicValue) - { - uint256 sellAmount = abi.decode(signature, (uint256)); - GPv2Order.Data memory order = orderForSellAmount(sellAmount); - - if (order.hash(domainSeparator) == hash) { - magicValue = GPv2EIP1271.MAGICVALUE; - } - } - - function orderForSellAmount(uint256 sellAmount) public view returns (GPv2Order.Data memory order) { - order.sellToken = sellToken; - order.buyToken = buyToken; - order.receiver = owner; - order.sellAmount = sellAmount; - order.buyAmount = buyAmountForSellAmount(sellAmount); - order.validTo = validTo; - order.appData = APPDATA; - order.feeAmount = totalFeeAmount.mul(sellAmount).div(totalSellAmount); - order.kind = GPv2Order.KIND_SELL; - // NOTE: We counter-intuitively set `partiallyFillable` to `false`, even - // if the smart order as a whole acts like a partially fillable order. - // This is done since, once a settlement commits to a specific sell - // amount, then it is expected to use it completely and not partially. - order.partiallyFillable = false; - order.sellTokenBalance = GPv2Order.BALANCE_ERC20; - order.buyTokenBalance = GPv2Order.BALANCE_ERC20; - } - - function buyAmountForSellAmount(uint256 sellAmount) private view returns (uint256 buyAmount) { - uint256 feeAdjustedBalance = - sellToken.balanceOf(address(this)).mul(totalSellAmount).div(totalSellAmount.add(totalFeeAmount)); - uint256 soldAmount = totalSellAmount > feeAdjustedBalance ? totalSellAmount - feeAdjustedBalance : 0; - - // NOTE: This is currently a silly price strategy where the xrate - // increases linearly from 1:1 to 1:2 as the smart order gets filled. - // This can be extended to more complex "price curves". - buyAmount = sellAmount.mul(totalSellAmount.add(sellAmount).add(soldAmount)).div(totalSellAmount); - } -} diff --git a/yarn.lock b/yarn.lock index 43e1761b..bba4b019 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" @@ -172,30 +167,6 @@ patch-package "^6.2.2" postinstall-postinstall "^2.1.0" -"@ethereumjs/common@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.5.0.tgz#ec61551b31bef7a69d1dc634d8932468866a4268" - integrity sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg== - dependencies: - crc-32 "^1.2.0" - ethereumjs-util "^7.1.1" - -"@ethereumjs/common@^2.5.0": - version "2.6.5" - resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.5.tgz#0a75a22a046272579d91919cb12d84f2756e8d30" - integrity sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA== - dependencies: - crc-32 "^1.2.0" - ethereumjs-util "^7.1.5" - -"@ethereumjs/tx@3.3.2": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.3.2.tgz#348d4624bf248aaab6c44fec2ae67265efe3db00" - integrity sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog== - dependencies: - "@ethereumjs/common" "^2.5.0" - ethereumjs-util "^7.1.2" - "@ethersproject/abi@5.0.0-beta.153": version "5.0.0-beta.153" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.0-beta.153.tgz#43a37172b33794e4562999f6e2d555b7599a8eee" @@ -211,7 +182,7 @@ "@ethersproject/properties" ">=5.0.0-beta.131" "@ethersproject/strings" ">=5.0.0-beta.130" -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -501,7 +472,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.6.2", "@ethersproject/transactions@^5.7.0": +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== @@ -568,11 +539,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@gnosis.pm/safe-contracts@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-contracts/-/safe-contracts-1.3.0.tgz#316741a7690d8751a1f701538cfc9ec80866eedc" - integrity sha512-1p+1HwGvxGUVzVkFjNzglwHrLNA67U/axP0Ct85FzzH8yhGJb4t9jDjPYocVMzLorDoWAfKicGy1akPY9jXRVw== - "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" @@ -593,14 +559,14 @@ integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@jridgewell/resolve-uri@^3.0.3": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -961,48 +927,6 @@ path-browserify "^1.0.0" url "^0.11.0" -"@safe-global/api-kit@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@safe-global/api-kit/-/api-kit-1.3.0.tgz#3606aa52a3d5d0d46817fafc38572be7be1eaf31" - integrity sha512-3fdvoBtgufzmqmoBHir7vbS5N2t9Yc4kTeIJmAgmAGl8rHAy3z1bSv5uoEHYSMow34q1Am1aUar6vVAwwkIXhg== - dependencies: - "@ethersproject/abstract-signer" "^5.7.0" - "@safe-global/safe-core-sdk-types" "^2.2.0" - node-fetch "^2.6.6" - -"@safe-global/protocol-kit@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@safe-global/protocol-kit/-/protocol-kit-1.2.0.tgz#5501fa0e2a1b4ad03cd5a4ee12bb9e799e1dd5a7" - integrity sha512-drU2uK30AZ4tqI/9ER7PGMD/lZp/5B9T02t+noTk7WF9Xb7HxskJd8GNU01KE55oyH31Y0AfXaE68H/f9lYa4A== - dependencies: - "@ethersproject/address" "^5.7.0" - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/solidity" "^5.7.0" - "@safe-global/safe-deployments" "^1.26.0" - ethereumjs-util "^7.1.5" - semver "^7.5.4" - web3 "^1.8.1" - web3-core "^1.8.1" - web3-utils "^1.8.1" - -"@safe-global/safe-core-sdk-types@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@safe-global/safe-core-sdk-types/-/safe-core-sdk-types-2.2.0.tgz#2e34a5089035719e9a92a0bc6aa181c2edb0f108" - integrity sha512-vVG9qQnUYx+Xwsbuqraq25MPJX1I1aV1P81ZnHZa1lEMU7stqYWAmykUm/mvqsm8+AsvEB/wBKlFjbFJ/duzoA== - dependencies: - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/contracts" "^5.7.0" - "@safe-global/safe-deployments" "^1.26.0" - web3-core "^1.8.1" - web3-utils "^1.8.1" - -"@safe-global/safe-deployments@^1.26.0": - version "1.26.0" - resolved "https://registry.yarnpkg.com/@safe-global/safe-deployments/-/safe-deployments-1.26.0.tgz#b83615b3b5a66e736e08f8ecf2801ed988e9e007" - integrity sha512-Tw89O4/paT19ieMoiWQbqRApb0Bef/DxweS9rxodXAM5EQModkbyFXGZca+YxXE67sLvWjLr2jJUOxwze8mhGw== - dependencies: - semver "^7.3.7" - "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" @@ -1098,7 +1022,7 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sindresorhus/is@^4.0.0", "@sindresorhus/is@^4.6.0": +"@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== @@ -1143,35 +1067,6 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@slack/logger@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@slack/logger/-/logger-3.0.0.tgz#b736d4e1c112c22a10ffab0c2d364620aedcb714" - integrity sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA== - dependencies: - "@types/node" ">=12.0.0" - -"@slack/types@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@slack/types/-/types-2.8.0.tgz#11ea10872262a7e6f86f54e5bcd4f91e3a41fe91" - integrity sha512-ghdfZSF0b4NC9ckBA8QnQgC9DJw2ZceDq0BIjjRSv6XAZBXJdWgxIsYz0TYnWSiqsKZGH2ZXbj9jYABZdH3OSQ== - -"@slack/web-api@^6.9.0": - version "6.9.0" - resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.9.0.tgz#d829dcfef490dbce8e338912706b6f39dcde3ad2" - integrity sha512-RME5/F+jvQmZHkoP+ogrDbixq1Ms1mBmylzuWq4sf3f7GCpMPWoiZ+WqWk+sism3vrlveKWIgO9R4Qg9fiRyoQ== - dependencies: - "@slack/logger" "^3.0.0" - "@slack/types" "^2.8.0" - "@types/is-stream" "^1.1.0" - "@types/node" ">=12.0.0" - axios "^0.27.2" - eventemitter3 "^3.1.0" - form-data "^2.5.0" - is-electron "2.2.2" - is-stream "^1.1.0" - p-queue "^6.6.1" - p-retry "^4.0.0" - "@solidity-parser/parser@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.0.tgz#d51f074efb0acce0e953ec48133561ed710cebc0" @@ -1233,24 +1128,24 @@ js-yaml "^3.14.0" "@tsconfig/node10@^1.0.7": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" - integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== "@tsconfig/node12@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" - integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== "@tsconfig/node14@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" - integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@typechain/ethers-v5@^2.0.0": version "2.0.0" @@ -1266,14 +1161,14 @@ dependencies: "@types/node" "*" -"@types/bn.js@^5.1.0", "@types/bn.js@^5.1.1": +"@types/bn.js@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== dependencies: "@types/node" "*" -"@types/cacheable-request@^6.0.1", "@types/cacheable-request@^6.0.2": +"@types/cacheable-request@^6.0.1": version "6.0.3" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== @@ -1339,13 +1234,6 @@ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== -"@types/is-stream@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" - integrity sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg== - dependencies: - "@types/node" "*" - "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -1403,11 +1291,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== -"@types/node@>=12.0.0": - version "20.5.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.3.tgz#fa52c147f405d56b2f1dd8780d840aa87ddff629" - integrity sha512-ITI7rbWczR8a/S6qjAW7DMqxqFMjjTo61qZVWJ1ubPvbIQsL5D/TvwjYEalM8Kthpe3hTzOGrF2TGbAu2uyqeA== - "@types/node@^10.0.3": version "10.17.56" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.56.tgz#010c9e047c3ff09ddcd11cbb6cf5912725cdc2b3" @@ -1467,11 +1350,6 @@ dependencies: "@types/node" "*" -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - "@types/secp256k1@^4.0.1": version "4.0.3" resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c" @@ -1637,11 +1515,6 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -abortcontroller-polyfill@^1.7.3: - version "1.7.5" - resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed" - integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ== - abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" @@ -1697,14 +1570,16 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" -acorn@^8.4.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.11.0, acorn@^8.4.1: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== acorn@^8.8.0: version "8.8.2" @@ -2084,23 +1959,6 @@ axios@^0.21.1: dependencies: follow-redirects "^1.14.0" -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - -axios@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.5.tgz#e07209b39a0d11848e3e341fa087acd71dadc542" - integrity sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw== - dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -2996,11 +2854,6 @@ cacheable-lookup@^5.0.3: resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== -cacheable-lookup@^6.0.4: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz#0330a543471c61faa4e9035db583aad753b36385" - integrity sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww== - cacheable-lookup@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" @@ -3583,13 +3436,6 @@ cross-fetch@^2.1.0, cross-fetch@^2.1.1: node-fetch "^2.6.7" whatwg-fetch "^2.0.4" -cross-fetch@^3.1.4: - version "3.1.8" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" - integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== - dependencies: - node-fetch "^2.6.12" - cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -4163,11 +4009,6 @@ es6-iterator@^2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-promise@^4.2.8: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" @@ -4734,7 +4575,7 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereum rlp "^2.0.0" safe-buffer "^5.1.1" -ethereumjs-util@^7.0.2, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.2, ethereumjs-util@^7.1.5: +ethereumjs-util@^7.0.2, ethereumjs-util@^7.1.0: version "7.1.5" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== @@ -4875,16 +4716,6 @@ eventemitter3@4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== -eventemitter3@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" - integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - events@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -5021,7 +4852,7 @@ fast-diff@^1.1.2, fast-diff@^1.2.0: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.0.3, fast-glob@^3.1.1: +fast-glob@^3.0.3: version "3.2.5" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== @@ -5200,11 +5031,6 @@ follow-redirects@^1.12.1, follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== -follow-redirects@^1.14.9, follow-redirects@^1.15.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== - for-each@^0.3.3, for-each@~0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -5222,17 +5048,12 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== -form-data-encoder@1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96" - integrity sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg== - form-data-encoder@^2.1.2: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== -form-data@^2.2.0, form-data@^2.5.0: +form-data@^2.2.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== @@ -5667,18 +5488,6 @@ globby@^10.0.1: merge2 "^1.2.3" slash "^3.0.0" -globby@^11.0.4: - version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" - integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -5698,25 +5507,6 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -got@12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/got/-/got-12.1.0.tgz#099f3815305c682be4fd6b0ee0726d8e4c6b0af4" - integrity sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig== - dependencies: - "@sindresorhus/is" "^4.6.0" - "@szmarczak/http-timer" "^5.0.1" - "@types/cacheable-request" "^6.0.2" - "@types/responselike" "^1.0.0" - cacheable-lookup "^6.0.4" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - form-data-encoder "1.7.1" - get-stream "^6.0.1" - http2-wrapper "^2.1.10" - lowercase-keys "^3.0.0" - p-cancelable "^3.0.0" - responselike "^2.0.0" - got@9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -6177,7 +5967,7 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.1.1, ignore@^5.1.4, ignore@^5.2.0: +ignore@^5.1.1, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== @@ -6426,11 +6216,6 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-electron@2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/is-electron/-/is-electron-2.2.2.tgz#3778902a2044d76de98036f5dc58089ac4d80bb9" - integrity sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg== - is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -6480,13 +6265,6 @@ is-function@^1.0.1: resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== -is-generator-function@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -6560,7 +6338,7 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== @@ -6590,13 +6368,6 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" -is-typed-array@^1.1.3: - version "1.1.12" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" - integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== - dependencies: - which-typed-array "^1.1.11" - is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -7870,13 +7641,6 @@ node-fetch@^2.6.1, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.12, node-fetch@^2.6.6: - version "2.6.12" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" - integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== - dependencies: - whatwg-url "^5.0.0" - node-fetch@~1.7.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -8066,13 +7830,6 @@ oboe@2.1.4: dependencies: http-https "^1.0.0" -oboe@2.1.5: - version "2.1.5" - resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.5.tgz#5554284c543a2266d7a38f17e073821fbde393cd" - integrity sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA== - dependencies: - http-https "^1.0.0" - on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -8151,11 +7908,6 @@ p-cancelable@^3.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -8205,29 +7957,6 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-queue@^6.6.1: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-retry@^4.0.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -8566,11 +8295,6 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -9117,11 +8841,6 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -9298,13 +9017,6 @@ semver@^7.5.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== -semver@^7.5.4: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - semver@~5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" @@ -10220,9 +9932,9 @@ ts-generator@^0.1.1: ts-essentials "^1.0.0" ts-node@^10.9.1: - version "10.9.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -10545,17 +10257,6 @@ util.promisify@^1.0.0: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.1" -util@^0.12.5: - version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" - integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - is-typed-array "^1.1.3" - which-typed-array "^1.1.2" - utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -10581,11 +10282,6 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" - integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -10618,15 +10314,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -web3-bzz@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.10.0.tgz#ac74bc71cdf294c7080a79091079192f05c5baed" - integrity sha512-o9IR59io3pDUsXTsps5pO5hW1D5zBmg46iNc2t4j2DkaYHNdDLwk2IP9ukoM2wg47QILfPEJYzhTfkS/CcX0KA== - dependencies: - "@types/node" "^12.12.6" - got "12.1.0" - swarm-js "^0.1.40" - web3-bzz@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.11.tgz#41bc19a77444bd5365744596d778b811880f707f" @@ -10637,14 +10324,6 @@ web3-bzz@1.2.11: swarm-js "^0.1.40" underscore "1.9.1" -web3-core-helpers@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.10.0.tgz#1016534c51a5df77ed4f94d1fcce31de4af37fad" - integrity sha512-pIxAzFDS5vnbXvfvLSpaA1tfRykAe9adw43YCKsEYQwH0gCLL0kMLkaCX3q+Q8EVmAh+e1jWL/nl9U0de1+++g== - dependencies: - web3-eth-iban "1.10.0" - web3-utils "1.10.0" - web3-core-helpers@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.11.tgz#84c681ed0b942c0203f3b324a245a127e8c67a99" @@ -10654,17 +10333,6 @@ web3-core-helpers@1.2.11: web3-eth-iban "1.2.11" web3-utils "1.2.11" -web3-core-method@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.10.0.tgz#82668197fa086e8cc8066742e35a9d72535e3412" - integrity sha512-4R700jTLAMKDMhQ+nsVfIXvH6IGJlJzGisIfMKWAIswH31h5AZz7uDUW2YctI+HrYd+5uOAlS4OJeeT9bIpvkA== - dependencies: - "@ethersproject/transactions" "^5.6.2" - web3-core-helpers "1.10.0" - web3-core-promievent "1.10.0" - web3-core-subscriptions "1.10.0" - web3-utils "1.10.0" - web3-core-method@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.11.tgz#f880137d1507a0124912bf052534f168b8d8fbb6" @@ -10677,13 +10345,6 @@ web3-core-method@1.2.11: web3-core-subscriptions "1.2.11" web3-utils "1.2.11" -web3-core-promievent@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.10.0.tgz#cbb5b3a76b888df45ed3a8d4d8d4f54ccb66a37b" - integrity sha512-68N7k5LWL5R38xRaKFrTFT2pm2jBNFaM4GioS00YjAKXRQ3KjmhijOMG3TICz6Aa5+6GDWYelDNx21YAeZ4YTg== - dependencies: - eventemitter3 "4.0.4" - web3-core-promievent@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.11.tgz#51fe97ca0ddec2f99bf8c3306a7a8e4b094ea3cf" @@ -10691,17 +10352,6 @@ web3-core-promievent@1.2.11: dependencies: eventemitter3 "4.0.4" -web3-core-requestmanager@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.10.0.tgz#4b34f6e05837e67c70ff6f6993652afc0d54c340" - integrity sha512-3z/JKE++Os62APml4dvBM+GAuId4h3L9ckUrj7ebEtS2AR0ixyQPbrBodgL91Sv7j7cQ3Y+hllaluqjguxvSaQ== - dependencies: - util "^0.12.5" - web3-core-helpers "1.10.0" - web3-providers-http "1.10.0" - web3-providers-ipc "1.10.0" - web3-providers-ws "1.10.0" - web3-core-requestmanager@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.11.tgz#fe6eb603fbaee18530293a91f8cf26d8ae28c45a" @@ -10713,14 +10363,6 @@ web3-core-requestmanager@1.2.11: web3-providers-ipc "1.2.11" web3-providers-ws "1.2.11" -web3-core-subscriptions@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.10.0.tgz#b534592ee1611788fc0cb0b95963b9b9b6eacb7c" - integrity sha512-HGm1PbDqsxejI075gxBc5OSkwymilRWZufIy9zEpnWKNmfbuv5FfHgW1/chtJP6aP3Uq2vHkvTDl3smQBb8l+g== - dependencies: - eventemitter3 "4.0.4" - web3-core-helpers "1.10.0" - web3-core-subscriptions@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.11.tgz#beca908fbfcb050c16f45f3f0f4c205e8505accd" @@ -10730,19 +10372,6 @@ web3-core-subscriptions@1.2.11: underscore "1.9.1" web3-core-helpers "1.2.11" -web3-core@1.10.0, web3-core@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.10.0.tgz#9aa07c5deb478cf356c5d3b5b35afafa5fa8e633" - integrity sha512-fWySwqy2hn3TL89w5TM8wXF1Z2Q6frQTKHWmP0ppRQorEK8NcHJRfeMiv/mQlSKoTS1F6n/nv2uyZsixFycjYQ== - dependencies: - "@types/bn.js" "^5.1.1" - "@types/node" "^12.12.6" - bignumber.js "^9.0.0" - web3-core-helpers "1.10.0" - web3-core-method "1.10.0" - web3-core-requestmanager "1.10.0" - web3-utils "1.10.0" - web3-core@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.11.tgz#1043cacc1becb80638453cc5b2a14be9050288a7" @@ -10756,14 +10385,6 @@ web3-core@1.2.11: web3-core-requestmanager "1.2.11" web3-utils "1.2.11" -web3-eth-abi@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.10.0.tgz#53a7a2c95a571e205e27fd9e664df4919483cce1" - integrity sha512-cwS+qRBWpJ43aI9L3JS88QYPfFcSJJ3XapxOQ4j40v6mk7ATpA8CVK1vGTzpihNlOfMVRBkR95oAj7oL6aiDOg== - dependencies: - "@ethersproject/abi" "^5.6.3" - web3-utils "1.10.0" - web3-eth-abi@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.11.tgz#a887494e5d447c2926d557a3834edd66e17af9b0" @@ -10773,22 +10394,6 @@ web3-eth-abi@1.2.11: underscore "1.9.1" web3-utils "1.2.11" -web3-eth-accounts@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.10.0.tgz#2942beca0a4291455f32cf09de10457a19a48117" - integrity sha512-wiq39Uc3mOI8rw24wE2n15hboLE0E9BsQLdlmsL4Zua9diDS6B5abXG0XhFcoNsXIGMWXVZz4TOq3u4EdpXF/Q== - dependencies: - "@ethereumjs/common" "2.5.0" - "@ethereumjs/tx" "3.3.2" - eth-lib "0.2.8" - ethereumjs-util "^7.1.5" - scrypt-js "^3.0.1" - uuid "^9.0.0" - web3-core "1.10.0" - web3-core-helpers "1.10.0" - web3-core-method "1.10.0" - web3-utils "1.10.0" - web3-eth-accounts@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.11.tgz#a9e3044da442d31903a7ce035a86d8fa33f90520" @@ -10806,20 +10411,6 @@ web3-eth-accounts@1.2.11: web3-core-method "1.2.11" web3-utils "1.2.11" -web3-eth-contract@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.10.0.tgz#8e68c7654576773ec3c91903f08e49d0242c503a" - integrity sha512-MIC5FOzP/+2evDksQQ/dpcXhSqa/2hFNytdl/x61IeWxhh6vlFeSjq0YVTAyIzdjwnL7nEmZpjfI6y6/Ufhy7w== - dependencies: - "@types/bn.js" "^5.1.1" - web3-core "1.10.0" - web3-core-helpers "1.10.0" - web3-core-method "1.10.0" - web3-core-promievent "1.10.0" - web3-core-subscriptions "1.10.0" - web3-eth-abi "1.10.0" - web3-utils "1.10.0" - web3-eth-contract@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.11.tgz#917065902bc27ce89da9a1da26e62ef663663b90" @@ -10835,20 +10426,6 @@ web3-eth-contract@1.2.11: web3-eth-abi "1.2.11" web3-utils "1.2.11" -web3-eth-ens@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.10.0.tgz#96a676524e0b580c87913f557a13ed810cf91cd9" - integrity sha512-3hpGgzX3qjgxNAmqdrC2YUQMTfnZbs4GeLEmy8aCWziVwogbuqQZ+Gzdfrym45eOZodk+lmXyLuAdqkNlvkc1g== - dependencies: - content-hash "^2.5.2" - eth-ens-namehash "2.0.8" - web3-core "1.10.0" - web3-core-helpers "1.10.0" - web3-core-promievent "1.10.0" - web3-eth-abi "1.10.0" - web3-eth-contract "1.10.0" - web3-utils "1.10.0" - web3-eth-ens@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.11.tgz#26d4d7f16d6cbcfff918e39832b939edc3162532" @@ -10864,14 +10441,6 @@ web3-eth-ens@1.2.11: web3-eth-contract "1.2.11" web3-utils "1.2.11" -web3-eth-iban@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.10.0.tgz#5a46646401965b0f09a4f58e7248c8a8cd22538a" - integrity sha512-0l+SP3IGhInw7Q20LY3IVafYEuufo4Dn75jAHT7c2aDJsIolvf2Lc6ugHkBajlwUneGfbRQs/ccYPQ9JeMUbrg== - dependencies: - bn.js "^5.2.1" - web3-utils "1.10.0" - web3-eth-iban@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.11.tgz#f5f73298305bc7392e2f188bf38a7362b42144ef" @@ -10880,18 +10449,6 @@ web3-eth-iban@1.2.11: bn.js "^4.11.9" web3-utils "1.2.11" -web3-eth-personal@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.10.0.tgz#94d525f7a29050a0c2a12032df150ac5ea633071" - integrity sha512-anseKn98w/d703eWq52uNuZi7GhQeVjTC5/svrBWEKob0WZ5kPdo+EZoFN0sp5a5ubbrk/E0xSl1/M5yORMtpg== - dependencies: - "@types/node" "^12.12.6" - web3-core "1.10.0" - web3-core-helpers "1.10.0" - web3-core-method "1.10.0" - web3-net "1.10.0" - web3-utils "1.10.0" - web3-eth-personal@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.11.tgz#a38b3942a1d87a62070ce0622a941553c3d5aa70" @@ -10904,24 +10461,6 @@ web3-eth-personal@1.2.11: web3-net "1.2.11" web3-utils "1.2.11" -web3-eth@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.10.0.tgz#38b905e2759697c9624ab080cfcf4e6c60b3a6cf" - integrity sha512-Z5vT6slNMLPKuwRyKGbqeGYC87OAy8bOblaqRTgg94CXcn/mmqU7iPIlG4506YdcdK3x6cfEDG7B6w+jRxypKA== - dependencies: - web3-core "1.10.0" - web3-core-helpers "1.10.0" - web3-core-method "1.10.0" - web3-core-subscriptions "1.10.0" - web3-eth-abi "1.10.0" - web3-eth-accounts "1.10.0" - web3-eth-contract "1.10.0" - web3-eth-ens "1.10.0" - web3-eth-iban "1.10.0" - web3-eth-personal "1.10.0" - web3-net "1.10.0" - web3-utils "1.10.0" - web3-eth@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.11.tgz#4c81fcb6285b8caf544058fba3ae802968fdc793" @@ -10941,15 +10480,6 @@ web3-eth@1.2.11: web3-net "1.2.11" web3-utils "1.2.11" -web3-net@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.10.0.tgz#be53e7f5dafd55e7c9013d49c505448b92c9c97b" - integrity sha512-NLH/N3IshYWASpxk4/18Ge6n60GEvWBVeM8inx2dmZJVmRI6SJIlUxbL8jySgiTn3MMZlhbdvrGo8fpUW7a1GA== - dependencies: - web3-core "1.10.0" - web3-core-method "1.10.0" - web3-utils "1.10.0" - web3-net@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.11.tgz#eda68ef25e5cdb64c96c39085cdb74669aabbe1b" @@ -10985,16 +10515,6 @@ web3-provider-engine@14.2.1: xhr "^2.2.0" xtend "^4.0.1" -web3-providers-http@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.10.0.tgz#864fa48675e7918c9a4374e5f664b32c09d0151b" - integrity sha512-eNr965YB8a9mLiNrkjAWNAPXgmQWfpBfkkn7tpEFlghfww0u3I0tktMZiaToJVcL2+Xq+81cxbkpeWJ5XQDwOA== - dependencies: - abortcontroller-polyfill "^1.7.3" - cross-fetch "^3.1.4" - es6-promise "^4.2.8" - web3-core-helpers "1.10.0" - web3-providers-http@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.11.tgz#1cd03442c61670572d40e4dcdf1faff8bd91e7c6" @@ -11003,14 +10523,6 @@ web3-providers-http@1.2.11: web3-core-helpers "1.2.11" xhr2-cookies "1.1.0" -web3-providers-ipc@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.10.0.tgz#9747c7a6aee96a51488e32fa7c636c3460b39889" - integrity sha512-OfXG1aWN8L1OUqppshzq8YISkWrYHaATW9H8eh0p89TlWMc1KZOL9vttBuaBEi96D/n0eYDn2trzt22bqHWfXA== - dependencies: - oboe "2.1.5" - web3-core-helpers "1.10.0" - web3-providers-ipc@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.11.tgz#d16d6c9be1be6e0b4f4536c4acc16b0f4f27ef21" @@ -11020,15 +10532,6 @@ web3-providers-ipc@1.2.11: underscore "1.9.1" web3-core-helpers "1.2.11" -web3-providers-ws@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.10.0.tgz#cb0b87b94c4df965cdf486af3a8cd26daf3975e5" - integrity sha512-sK0fNcglW36yD5xjnjtSGBnEtf59cbw4vZzJ+CmOWIKGIR96mP5l684g0WD0Eo+f4NQc2anWWXG74lRc9OVMCQ== - dependencies: - eventemitter3 "4.0.4" - web3-core-helpers "1.10.0" - websocket "^1.0.32" - web3-providers-ws@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.11.tgz#a1dfd6d9778d840561d9ec13dd453046451a96bb" @@ -11039,16 +10542,6 @@ web3-providers-ws@1.2.11: web3-core-helpers "1.2.11" websocket "^1.0.31" -web3-shh@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.10.0.tgz#c2979b87e0f67a7fef2ce9ee853bd7bfbe9b79a8" - integrity sha512-uNUUuNsO2AjX41GJARV9zJibs11eq6HtOe6Wr0FtRUcj8SN6nHeYIzwstAvJ4fXA53gRqFMTxdntHEt9aXVjpg== - dependencies: - web3-core "1.10.0" - web3-core-method "1.10.0" - web3-core-subscriptions "1.10.0" - web3-net "1.10.0" - web3-shh@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.11.tgz#f5d086f9621c9a47e98d438010385b5f059fd88f" @@ -11059,19 +10552,6 @@ web3-shh@1.2.11: web3-core-subscriptions "1.2.11" web3-net "1.2.11" -web3-utils@1.10.0, web3-utils@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.0.tgz#ca4c1b431a765c14ac7f773e92e0fd9377ccf578" - integrity sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg== - dependencies: - bn.js "^5.2.1" - ethereum-bloom-filters "^1.0.6" - ethereumjs-util "^7.1.0" - ethjs-unit "0.1.6" - number-to-bn "1.7.0" - randombytes "^2.1.0" - utf8 "3.0.0" - web3-utils@1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.11.tgz#af1942aead3fb166ae851a985bed8ef2c2d95a82" @@ -11112,19 +10592,6 @@ web3@1.2.11: web3-shh "1.2.11" web3-utils "1.2.11" -web3@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-1.10.0.tgz#2fde0009f59aa756c93e07ea2a7f3ab971091274" - integrity sha512-YfKY9wSkGcM8seO+daR89oVTcbu18NsVfvOngzqMYGUU0pPSQmE57qQDvQzUeoIOHAnXEBNzrhjQJmm8ER0rng== - dependencies: - web3-bzz "1.10.0" - web3-core "1.10.0" - web3-eth "1.10.0" - web3-eth-personal "1.10.0" - web3-net "1.10.0" - web3-shh "1.10.0" - web3-utils "1.10.0" - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -11142,7 +10609,7 @@ websocket@1.0.32: utf-8-validate "^5.0.2" yaeti "^0.0.6" -websocket@^1.0.31, websocket@^1.0.32: +websocket@^1.0.31: version "1.0.34" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== @@ -11188,17 +10655,6 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which-typed-array@^1.1.11, which-typed-array@^1.1.2: - version "1.1.11" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" - integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - which-typed-array@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"