Compare commits
4 Commits
jacob/chor
...
feat/multi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2507028612 | ||
|
|
90b826da61 | ||
|
|
d022f2864b | ||
|
|
5bbcc353f8 |
209
.github/workflows/ci.yml
vendored
209
.github/workflows/ci.yml
vendored
@@ -1,115 +1,108 @@
|
||||
name: Continuous Integration
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- development
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- development
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build solution
|
||||
run: yarn build
|
||||
|
||||
- name: Lint typescript
|
||||
run: yarn lint:ts
|
||||
|
||||
- name: Lint solidity
|
||||
run: yarn lint:contracts
|
||||
|
||||
- name: Run prettier
|
||||
run: yarn prettier:ci
|
||||
|
||||
- name: Check dependent packages have consistent versions
|
||||
run: yarn deps_versions:ci
|
||||
|
||||
- name: Check diff in docs
|
||||
run: yarn diff_md_docs:ci
|
||||
|
||||
- name: Check for broken links in markdown files
|
||||
run: yarn test:links
|
||||
|
||||
- name: Test doc generation
|
||||
run: yarn test:generate_docs:ci
|
||||
|
||||
- name: Test @0x/contracts-*
|
||||
run: |
|
||||
yarn wsrun \
|
||||
-p @0x/contracts-multisig \
|
||||
-p @0x/contracts-utils \
|
||||
-p @0x/contracts-exchange-libs \
|
||||
-p @0x/contracts-erc20 \
|
||||
-p @0x/contracts-erc721 \
|
||||
-p @0x/contracts-erc1155 \
|
||||
-p @0x/contracts-asset-proxy \
|
||||
-p @0x/contracts-broker \
|
||||
-p @0x/contracts-zero-ex \
|
||||
-m --serial -c test:ci
|
||||
|
||||
- name: Test local @0x/contracts-*
|
||||
run: |
|
||||
yarn wsrun \
|
||||
-p @0x/contracts-test-utils \
|
||||
-p @0x/contract-addresses \
|
||||
-p @0x/contract-artifacts \
|
||||
-p @0x/contract-wrappers-test \
|
||||
-p @0x/order-utils \
|
||||
-m --serial -c test:ci
|
||||
|
||||
- name: Add foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Run Forge build
|
||||
working-directory: contracts/zero-ex
|
||||
run: |
|
||||
forge --version
|
||||
forge build --sizes
|
||||
|
||||
- name: Run Forge tests
|
||||
working-directory: contracts/zero-ex
|
||||
env:
|
||||
ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }}
|
||||
AVALANCHE_RPC_URL: ${{ secrets.AVALANCHE_RPC_URL }}
|
||||
BSC_RPC_URL: ${{ secrets.BSC_RPC_URL }}
|
||||
FANTOM_RPC_URL: ${{ secrets.FANTOM_RPC_URL }}
|
||||
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
|
||||
OPTIMISM_RPC_URL: ${{ secrets.OPTIMISM_RPC_URL }}
|
||||
POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }}
|
||||
AVALANCHE_RPC_URL: ${{ secrets.AVALANCHE_RPC_URL }}
|
||||
BSC_RPC_URL: ${{ secrets.BSC_RPC_URL }}
|
||||
FANTOM_RPC_URL: ${{ secrets.FANTOM_RPC_URL }}
|
||||
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
|
||||
OPTIMISM_RPC_URL: ${{ secrets.OPTIMISM_RPC_URL }}
|
||||
POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }}
|
||||
run: |
|
||||
forge test -vvv --gas-report
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Run Forge coverage
|
||||
working-directory: contracts/zero-ex
|
||||
run: |
|
||||
forge coverage --report lcov
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build solution
|
||||
run: yarn build
|
||||
|
||||
- name: Lint typescript
|
||||
run: yarn lint:ts
|
||||
|
||||
- name: Lint solidity
|
||||
run: yarn lint:contracts
|
||||
|
||||
- name: Run prettier
|
||||
run: yarn prettier:ci
|
||||
|
||||
- name: Check dependent packages have consistent versions
|
||||
run: yarn deps_versions:ci
|
||||
|
||||
- name: Check diff in docs
|
||||
run: yarn diff_md_docs:ci
|
||||
|
||||
- name: Check for broken links in markdown files
|
||||
run: yarn test:links
|
||||
|
||||
- name: Test doc generation
|
||||
run: yarn test:generate_docs:ci
|
||||
|
||||
- name: Test @0x/contracts-*
|
||||
run: |
|
||||
yarn wsrun \
|
||||
-p @0x/contracts-multisig \
|
||||
-p @0x/contracts-utils \
|
||||
-p @0x/contracts-exchange-libs \
|
||||
-p @0x/contracts-erc20 \
|
||||
-p @0x/contracts-erc721 \
|
||||
-p @0x/contracts-erc1155 \
|
||||
-p @0x/contracts-asset-proxy \
|
||||
-p @0x/contracts-broker \
|
||||
-p @0x/contracts-zero-ex \
|
||||
-m --serial -c test:ci
|
||||
|
||||
- name: Test local @0x/contracts-*
|
||||
run: |
|
||||
yarn wsrun \
|
||||
-p @0x/contracts-test-utils \
|
||||
-p @0x/contract-addresses \
|
||||
-p @0x/contract-artifacts \
|
||||
-p @0x/contract-wrappers-test \
|
||||
-p @0x/order-utils \
|
||||
-m --serial -c test:ci
|
||||
|
||||
- name: Add foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Run Forge build
|
||||
working-directory: contracts/zero-ex
|
||||
run: |
|
||||
forge --version
|
||||
forge build --sizes
|
||||
|
||||
- name: Run Forge tests
|
||||
working-directory: contracts/zero-ex
|
||||
run: |
|
||||
forge test -vvv --gas-report
|
||||
|
||||
- name: Run Forge coverage
|
||||
working-directory: contracts/zero-ex
|
||||
run: |
|
||||
forge coverage --report summary --report lcov
|
||||
|
||||
- name: Upload the coverage report to Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
base-path: ./contracts/zero-ex/
|
||||
path-to-lcov: ./contracts/zero-ex/lcov.info
|
||||
|
||||
- name: Check coverage threshold
|
||||
uses: VeryGoodOpenSource/very_good_coverage@v2
|
||||
with:
|
||||
path: ./contracts/zero-ex/lcov.info
|
||||
min_coverage: 6.98
|
||||
exclude: '**/tests'
|
||||
- name: Upload the coverage report to Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
base-path: ./contracts/zero-ex/
|
||||
path-to-lcov: ./contracts/zero-ex/lcov.info
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2023 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6.5;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "./IERC20TokenV06.sol";
|
||||
|
||||
contract MintableERC20TokenV06 is IERC20TokenV06 {
|
||||
using LibSafeMathV06 for uint256;
|
||||
|
||||
uint8 public override decimals = 18;
|
||||
mapping(address => uint256) internal balances;
|
||||
mapping(address => mapping(address => uint256)) internal allowed;
|
||||
|
||||
uint256 internal _totalSupply;
|
||||
|
||||
/// @dev send `value` token to `to` from `msg.sender`
|
||||
/// @param _to The address of the recipient
|
||||
/// @param _value The amount of token to be transferred
|
||||
/// @return True if transfer was successful
|
||||
function transfer(address _to, uint256 _value) external override returns (bool) {
|
||||
require(balances[msg.sender] >= _value, "ERC20_INSUFFICIENT_BALANCE");
|
||||
require(balances[_to].safeAdd(_value) >= balances[_to], "UINT256_OVERFLOW");
|
||||
|
||||
balances[msg.sender] = balances[msg.sender].safeSub(_value);
|
||||
balances[_to] = balances[_to].safeAdd(_value);
|
||||
|
||||
emit Transfer(msg.sender, _to, _value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @dev send `value` token to `to` from `from` on the condition it is approved by `from`
|
||||
/// @param _from The address of the sender
|
||||
/// @param _to The address of the recipient
|
||||
/// @param _value The amount of token to be transferred
|
||||
/// @return True if transfer was successful
|
||||
function transferFrom(address _from, address _to, uint256 _value) external override returns (bool) {
|
||||
require(balances[_from] >= _value, "ERC20_INSUFFICIENT_BALANCE");
|
||||
require(allowed[_from][msg.sender] >= _value, "ERC20_INSUFFICIENT_ALLOWANCE");
|
||||
require(balances[_to].safeAdd(_value) >= balances[_to], "UINT256_OVERFLOW");
|
||||
|
||||
balances[_to] = balances[_to].safeAdd(_value);
|
||||
balances[_from] = balances[_from].safeSub(_value);
|
||||
allowed[_from][msg.sender] = allowed[_from][msg.sender].safeSub(_value);
|
||||
|
||||
emit Transfer(_from, _to, _value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @dev `msg.sender` approves `_spender` to spend `_value` tokens
|
||||
/// @param _spender The address of the account able to transfer the tokens
|
||||
/// @param _value The amount of wei to be approved for transfer
|
||||
/// @return Always true if the call has enough gas to complete execution
|
||||
function approve(address _spender, uint256 _value) external override returns (bool) {
|
||||
allowed[msg.sender][_spender] = _value;
|
||||
emit Approval(msg.sender, _spender, _value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @dev Query total supply of token
|
||||
/// @return Total supply of token
|
||||
function totalSupply() external view override returns (uint256) {
|
||||
return _totalSupply;
|
||||
}
|
||||
|
||||
/// @dev Query the balance of owner
|
||||
/// @param _owner The address from which the balance will be retrieved
|
||||
/// @return Balance of owner
|
||||
function balanceOf(address _owner) external view override returns (uint256) {
|
||||
return balances[_owner];
|
||||
}
|
||||
|
||||
/// @param _owner The address of the account owning tokens
|
||||
/// @param _spender The address of the account able to transfer the tokens
|
||||
/// @return Amount of remaining tokens allowed to spent
|
||||
function allowance(address _owner, address _spender) external view override returns (uint256) {
|
||||
return allowed[_owner][_spender];
|
||||
}
|
||||
|
||||
/// @dev Mints new tokens
|
||||
/// @param _to Address of the beneficiary that will own the minted token
|
||||
/// @param _value Amount of tokens to mint
|
||||
function _mint(address _to, uint256 _value) internal {
|
||||
balances[_to] = _value.safeAdd(balances[_to]);
|
||||
_totalSupply = _totalSupply.safeAdd(_value);
|
||||
|
||||
emit Transfer(address(0), _to, _value);
|
||||
}
|
||||
|
||||
/// @dev Mints new tokens
|
||||
/// @param _owner Owner of tokens that will be burned
|
||||
/// @param _value Amount of tokens to burn
|
||||
function _burn(address _owner, uint256 _value) internal {
|
||||
balances[_owner] = balances[_owner].safeSub(_value);
|
||||
_totalSupply = _totalSupply.safeSub(_value);
|
||||
|
||||
emit Transfer(_owner, address(0), _value);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@
|
||||
"./contracts/src/transformers/LibERC20Transformer.sol",
|
||||
"./contracts/src/transformers/LogMetadataTransformer.sol",
|
||||
"./contracts/src/transformers/PayTakerTransformer.sol",
|
||||
"./contracts/src/transformers/TradeSurplusTransformer.sol",
|
||||
"./contracts/src/transformers/PositiveSlippageFeeTransformer.sol",
|
||||
"./contracts/src/transformers/Transformer.sol",
|
||||
"./contracts/src/transformers/WethTransformer.sol",
|
||||
"./contracts/src/transformers/bridges/AbstractBridgeAdapter.sol",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
@@ -27,6 +28,7 @@ import "../migrations/LibMigrate.sol";
|
||||
import "../storage/LibMetaTransactionsStorage.sol";
|
||||
import "./interfaces/IFeature.sol";
|
||||
import "./interfaces/IMetaTransactionsFeature.sol";
|
||||
import "./interfaces/IMultiplexFeature.sol";
|
||||
import "./interfaces/INativeOrdersFeature.sol";
|
||||
import "./interfaces/ITransformERC20Feature.sol";
|
||||
import "./libs/LibSignature.sol";
|
||||
@@ -91,6 +93,9 @@ contract MetaTransactionsFeature is
|
||||
")"
|
||||
);
|
||||
|
||||
/// @dev The WETH token contract.
|
||||
IEtherTokenV06 private immutable WETH;
|
||||
|
||||
/// @dev Refunds up to `msg.value` leftover ETH at the end of the call.
|
||||
modifier refundsAttachedEth() {
|
||||
_;
|
||||
@@ -108,7 +113,12 @@ contract MetaTransactionsFeature is
|
||||
require(initialBalance <= address(this).balance, "MetaTransactionsFeature/ETH_LEAK");
|
||||
}
|
||||
|
||||
constructor(address zeroExAddress) public FixinCommon() FixinEIP712(zeroExAddress) {}
|
||||
constructor(
|
||||
address zeroExAddress,
|
||||
IEtherTokenV06 weth
|
||||
) public FixinCommon() FixinEIP712(zeroExAddress) {
|
||||
WETH = weth;
|
||||
}
|
||||
|
||||
/// @dev Initialize and register this feature.
|
||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||
@@ -245,6 +255,14 @@ contract MetaTransactionsFeature is
|
||||
returnResult = _executeFillLimitOrderCall(state);
|
||||
} else if (state.selector == INativeOrdersFeature.fillRfqOrder.selector) {
|
||||
returnResult = _executeFillRfqOrderCall(state);
|
||||
} else if (state.selector == IMultiplexFeature.multiplexBatchSellTokenForToken.selector) {
|
||||
returnResult = _executeMultiplexBatchSellTokenForTokenCall(state);
|
||||
} else if (state.selector == IMultiplexFeature.multiplexBatchSellTokenForEth.selector) {
|
||||
returnResult = _executeMultiplexBatchSellTokenForEthCall(state);
|
||||
} else if (state.selector == IMultiplexFeature.multiplexMultiHopSellTokenForToken.selector) {
|
||||
returnResult = _executeMultiplexMultiHopSellTokenForTokenCall(state);
|
||||
} else if (state.selector == IMultiplexFeature.multiplexMultiHopSellTokenForEth.selector) {
|
||||
returnResult = _executeMultiplexMultiHopSellTokenForEthCall(state);
|
||||
} else {
|
||||
LibMetaTransactionsRichErrors.MetaTransactionUnsupportedFunctionError(state.hash, state.selector).rrevert();
|
||||
}
|
||||
@@ -426,7 +444,7 @@ contract MetaTransactionsFeature is
|
||||
|
||||
/// @dev Execute a `INativeOrdersFeature.fillRfqOrder()` meta-transaction call
|
||||
/// by decoding the call args and translating the call to the internal
|
||||
/// `INativeOrdersFeature._fillRfqOrder()` variant, where we can overrideunimpleme
|
||||
/// `INativeOrdersFeature._fillRfqOrder()` variant, where we can override
|
||||
/// the taker address.
|
||||
function _executeFillRfqOrderCall(ExecuteState memory state) private returns (bytes memory returnResult) {
|
||||
LibNativeOrder.RfqOrder memory order;
|
||||
@@ -455,6 +473,162 @@ contract MetaTransactionsFeature is
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Execute a `IMultiplexFeature.multiplexBatchSellTokenForToken()` meta-transaction
|
||||
/// call by decoding the call args and translating the call to the internal
|
||||
/// `IMultiplexFeature._multiplexBatchSell()` variant, where we can override the
|
||||
/// msgSender address.
|
||||
function _executeMultiplexBatchSellTokenForTokenCall(ExecuteState memory state) private returns (bytes memory returnResult) {
|
||||
IERC20TokenV06 inputToken;
|
||||
IERC20TokenV06 outputToken;
|
||||
IMultiplexFeature.BatchSellSubcall[] memory calls;
|
||||
uint256 sellAmount;
|
||||
uint256 minBuyAmount;
|
||||
|
||||
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
|
||||
(inputToken, outputToken, calls, sellAmount, minBuyAmount) = abi.decode(
|
||||
args,
|
||||
(IERC20TokenV06, IERC20TokenV06, IMultiplexFeature.BatchSellSubcall[], uint256, uint256)
|
||||
);
|
||||
|
||||
return
|
||||
_callSelf(
|
||||
state.hash,
|
||||
abi.encodeWithSelector(
|
||||
IMultiplexFeature._multiplexBatchSell.selector,
|
||||
IMultiplexFeature.BatchSellParams({
|
||||
inputToken: inputToken,
|
||||
outputToken: outputToken,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: state.mtx.signer,
|
||||
msgSender: state.mtx.signer
|
||||
}),
|
||||
minBuyAmount
|
||||
),
|
||||
state.mtx.value
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Execute a `IMultiplexFeature.multiplexBatchSellTokenForEth()` meta-transaction
|
||||
/// call by decoding the call args and translating the call to the internal
|
||||
/// `IMultiplexFeature._multiplexBatchSellTokenForEth()` variant, where we can override the
|
||||
/// msgSender address.
|
||||
function _executeMultiplexBatchSellTokenForEthCall(ExecuteState memory state) private returns (bytes memory returnResult) {
|
||||
IERC20TokenV06 inputToken;
|
||||
IMultiplexFeature.BatchSellSubcall[] memory calls;
|
||||
uint256 sellAmount;
|
||||
uint256 minBuyAmount;
|
||||
|
||||
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
|
||||
(inputToken, calls, sellAmount, minBuyAmount) = abi.decode(
|
||||
args,
|
||||
(IERC20TokenV06, IMultiplexFeature.BatchSellSubcall[], uint256, uint256)
|
||||
);
|
||||
|
||||
returnResult = _callSelf(
|
||||
state.hash,
|
||||
abi.encodeWithSelector(
|
||||
IMultiplexFeature._multiplexBatchSell.selector,
|
||||
IMultiplexFeature.BatchSellParams({
|
||||
inputToken: inputToken,
|
||||
outputToken: IERC20TokenV06(WETH),
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: address(this),
|
||||
msgSender: state.mtx.signer
|
||||
}),
|
||||
minBuyAmount
|
||||
),
|
||||
state.mtx.value
|
||||
);
|
||||
|
||||
// Unwrap and transfer WETH
|
||||
uint256 boughtAmount = abi.decode(returnResult, (uint256));
|
||||
WETH.withdraw(boughtAmount);
|
||||
_transferEth(state.mtx.signer, boughtAmount);
|
||||
}
|
||||
|
||||
/// @dev Execute a `IMultiplexFeature.multiplexMultiHopSellTokenForToken()` meta-transaction
|
||||
/// call by decoding the call args and translating the call to the internal
|
||||
/// `IMultiplexFeature._multiplexMultiHopSell()` variant, where we can override the
|
||||
/// msgSender address.
|
||||
function _executeMultiplexMultiHopSellTokenForTokenCall(ExecuteState memory state) private returns (bytes memory returnResult) {
|
||||
address[] memory tokens;
|
||||
IMultiplexFeature.MultiHopSellSubcall[] memory calls;
|
||||
uint256 sellAmount;
|
||||
uint256 minBuyAmount;
|
||||
|
||||
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
|
||||
(tokens, calls, sellAmount, minBuyAmount) = abi.decode(
|
||||
args,
|
||||
(address[], IMultiplexFeature.MultiHopSellSubcall[], uint256, uint256)
|
||||
);
|
||||
|
||||
return
|
||||
_callSelf(
|
||||
state.hash,
|
||||
abi.encodeWithSelector(
|
||||
IMultiplexFeature._multiplexMultiHopSell.selector,
|
||||
IMultiplexFeature.MultiHopSellParams({
|
||||
tokens: tokens,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: state.mtx.signer,
|
||||
msgSender: state.mtx.signer
|
||||
}),
|
||||
minBuyAmount
|
||||
),
|
||||
state.mtx.value
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Execute a `IMultiplexFeature.multiplexMultiHopSellTokenForEth()` meta-transaction
|
||||
/// call by decoding the call args and translating the call to the internal
|
||||
/// `IMultiplexFeature._multiplexMultiHopSellTokenForEth()` variant, where we can override the
|
||||
/// msgSender address.
|
||||
function _executeMultiplexMultiHopSellTokenForEthCall(ExecuteState memory state) private returns (bytes memory returnResult) {
|
||||
address[] memory tokens;
|
||||
IMultiplexFeature.MultiHopSellSubcall[] memory calls;
|
||||
uint256 sellAmount;
|
||||
uint256 minBuyAmount;
|
||||
|
||||
bytes memory args = _extractArgumentsFromCallData(state.mtx.callData);
|
||||
(tokens, calls, sellAmount, minBuyAmount) = abi.decode(
|
||||
args,
|
||||
(address[], IMultiplexFeature.MultiHopSellSubcall[], uint256, uint256)
|
||||
);
|
||||
|
||||
require(
|
||||
tokens[tokens.length - 1] == address(WETH),
|
||||
"MetaTransactionsFeature::multiplexMultiHopSellTokenForEth/NOT_WETH"
|
||||
);
|
||||
|
||||
returnResult = _callSelf(
|
||||
state.hash,
|
||||
abi.encodeWithSelector(
|
||||
IMultiplexFeature._multiplexMultiHopSell.selector,
|
||||
IMultiplexFeature.MultiHopSellParams({
|
||||
tokens: tokens,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: address(this),
|
||||
msgSender: state.mtx.signer
|
||||
}),
|
||||
minBuyAmount
|
||||
),
|
||||
state.mtx.value
|
||||
);
|
||||
|
||||
// Unwrap and transfer WETH
|
||||
uint256 boughtAmount = abi.decode(returnResult, (uint256));
|
||||
WETH.withdraw(boughtAmount);
|
||||
_transferEth(state.mtx.signer, boughtAmount);
|
||||
}
|
||||
|
||||
/// @dev Make an arbitrary internal, meta-transaction call.
|
||||
/// Warning: Do not let unadulterated `callData` into this function.
|
||||
function _callSelf(bytes32 hash, bytes memory callData, uint256 value) private returns (bytes memory returnResult) {
|
||||
|
||||
@@ -70,6 +70,7 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
|
||||
_registerFeatureFunction(this.sellEthForTokenToUniswapV3.selector);
|
||||
_registerFeatureFunction(this.sellTokenForEthToUniswapV3.selector);
|
||||
_registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector);
|
||||
_registerFeatureFunction(this._sellTokenForTokenToUniswapV3.selector);
|
||||
_registerFeatureFunction(this._sellHeldTokenForTokenToUniswapV3.selector);
|
||||
_registerFeatureFunction(this.uniswapV3SwapCallback.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
@@ -139,6 +140,23 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
|
||||
buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, msg.sender, _normalizeRecipient(recipient));
|
||||
}
|
||||
|
||||
/// @dev Sell a token for another token directly against uniswap v3. Internal variant.
|
||||
/// @param encodedPath Uniswap-encoded path.
|
||||
/// @param sellAmount amount of the first token in the path to sell.
|
||||
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
|
||||
/// @param recipient The recipient of the bought tokens. Can be zero for payer.
|
||||
/// @param payer The address to pull the sold tokens from.
|
||||
/// @return buyAmount Amount of the last token in the path bought.
|
||||
function _sellTokenForTokenToUniswapV3(
|
||||
bytes memory encodedPath,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
||||
address recipient,
|
||||
address payer
|
||||
) public override onlySelf returns (uint256 buyAmount) {
|
||||
buyAmount = _swap(encodedPath, sellAmount, minBuyAmount, payer, _normalizeRecipient(recipient, payer));
|
||||
}
|
||||
|
||||
/// @dev Sell a token for another token directly against uniswap v3.
|
||||
/// Private variant, uses tokens held by `address(this)`.
|
||||
/// @param encodedPath Uniswap-encoded path.
|
||||
@@ -337,8 +355,13 @@ contract UniswapV3Feature is IFeature, IUniswapV3Feature, FixinCommon, FixinToke
|
||||
}
|
||||
}
|
||||
|
||||
// Convert null address values to fallback.
|
||||
function _normalizeRecipient(address recipient, address alternative) private pure returns (address payable normalizedRecipient) {
|
||||
return recipient == address(0) ? payable(alternative) : payable(recipient);
|
||||
}
|
||||
|
||||
// Convert null address values to msg.sender.
|
||||
function _normalizeRecipient(address recipient) private view returns (address payable normalizedRecipient) {
|
||||
return recipient == address(0) ? msg.sender : payable(recipient);
|
||||
return _normalizeRecipient(recipient, msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ interface IMultiplexFeature {
|
||||
bool useSelfBalance;
|
||||
// The recipient of the bought output tokens.
|
||||
address recipient;
|
||||
// The sender of the transaction.
|
||||
address msgSender;
|
||||
}
|
||||
|
||||
// Represents a constituent call of a batch sell.
|
||||
@@ -75,6 +77,8 @@ interface IMultiplexFeature {
|
||||
bool useSelfBalance;
|
||||
// The recipient of the bought output tokens.
|
||||
address recipient;
|
||||
// The sender of the transaction.
|
||||
address msgSender;
|
||||
}
|
||||
|
||||
// Represents a constituent call of a multi-hop sell.
|
||||
@@ -153,6 +157,17 @@ interface IMultiplexFeature {
|
||||
uint256 minBuyAmount
|
||||
) external returns (uint256 boughtAmount);
|
||||
|
||||
/// @dev Executes a multiplex BatchSell using the given
|
||||
/// parameters. Internal only.
|
||||
/// @param params The parameters for the BatchSell.
|
||||
/// @param minBuyAmount The minimum amount of `params.outputToken`
|
||||
/// that must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of `params.outputToken` bought.
|
||||
function _multiplexBatchSell(
|
||||
BatchSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) external returns (uint256 boughtAmount);
|
||||
|
||||
/// @dev Sells attached ETH via the given sequence of tokens
|
||||
/// and calls. `tokens[0]` must be WETH.
|
||||
/// The last token in `tokens` is the output token that
|
||||
@@ -204,4 +219,15 @@ interface IMultiplexFeature {
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount
|
||||
) external returns (uint256 boughtAmount);
|
||||
|
||||
/// @dev Executes a multiplex MultiHopSell using the given
|
||||
/// parameters. Internal only.
|
||||
/// @param params The parameters for the MultiHopSell.
|
||||
/// @param minBuyAmount The minimum amount of the output token
|
||||
/// that must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of the output token bought.
|
||||
function _multiplexMultiHopSell(
|
||||
MultiHopSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) external returns (uint256 boughtAmount);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,21 @@ interface IUniswapV3Feature {
|
||||
address recipient
|
||||
) external returns (uint256 buyAmount);
|
||||
|
||||
/// @dev Sell a token for another token directly against uniswap v3. Internal variant.
|
||||
/// @param encodedPath Uniswap-encoded path.
|
||||
/// @param sellAmount amount of the first token in the path to sell.
|
||||
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
|
||||
/// @param recipient The recipient of the bought tokens. Can be zero for payer.
|
||||
/// @param payer The address to pull the sold tokens from.
|
||||
/// @return buyAmount Amount of the last token in the path bought.
|
||||
function _sellTokenForTokenToUniswapV3(
|
||||
bytes memory encodedPath,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
||||
address recipient,
|
||||
address payer
|
||||
) external returns (uint256 buyAmount);
|
||||
|
||||
/// @dev Sell a token for another token directly against uniswap v3.
|
||||
/// Private variant, uses tokens held by `address(this)`.
|
||||
/// @param encodedPath Uniswap-encoded path.
|
||||
|
||||
@@ -80,9 +80,11 @@ contract MultiplexFeature is
|
||||
_registerFeatureFunction(this.multiplexBatchSellEthForToken.selector);
|
||||
_registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector);
|
||||
_registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector);
|
||||
_registerFeatureFunction(this._multiplexBatchSell.selector);
|
||||
_registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector);
|
||||
_registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector);
|
||||
_registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector);
|
||||
_registerFeatureFunction(this._multiplexMultiHopSell.selector);
|
||||
return LibMigrate.MIGRATE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -103,14 +105,15 @@ contract MultiplexFeature is
|
||||
// WETH is now held by this contract,
|
||||
// so `useSelfBalance` is true.
|
||||
return
|
||||
_multiplexBatchSell(
|
||||
_multiplexBatchSellPrivate(
|
||||
BatchSellParams({
|
||||
inputToken: WETH,
|
||||
outputToken: outputToken,
|
||||
sellAmount: msg.value,
|
||||
calls: calls,
|
||||
useSelfBalance: true,
|
||||
recipient: msg.sender
|
||||
recipient: msg.sender,
|
||||
msgSender: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
@@ -133,14 +136,15 @@ contract MultiplexFeature is
|
||||
// The outputToken is implicitly WETH. The `recipient`
|
||||
// of the WETH is set to this contract, since we
|
||||
// must unwrap the WETH and transfer the resulting ETH.
|
||||
boughtAmount = _multiplexBatchSell(
|
||||
boughtAmount = _multiplexBatchSellPrivate(
|
||||
BatchSellParams({
|
||||
inputToken: inputToken,
|
||||
outputToken: WETH,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: address(this)
|
||||
recipient: address(this),
|
||||
msgSender: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
@@ -167,26 +171,41 @@ contract MultiplexFeature is
|
||||
uint256 minBuyAmount
|
||||
) public override returns (uint256 boughtAmount) {
|
||||
return
|
||||
_multiplexBatchSell(
|
||||
_multiplexBatchSellPrivate(
|
||||
BatchSellParams({
|
||||
inputToken: inputToken,
|
||||
outputToken: outputToken,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: msg.sender
|
||||
recipient: msg.sender,
|
||||
msgSender: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Executes a batch sell and checks that at least
|
||||
/// `minBuyAmount` of `outputToken` was bought. Internal
|
||||
/// variant.
|
||||
/// @param params Batch sell parameters.
|
||||
/// @param minBuyAmount The minimum amount of `outputToken` that
|
||||
/// must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function _multiplexBatchSell(
|
||||
BatchSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) public override onlySelf returns (uint256 boughtAmount) {
|
||||
return _multiplexBatchSellPrivate(params, minBuyAmount);
|
||||
}
|
||||
|
||||
/// @dev Executes a batch sell and checks that at least
|
||||
/// `minBuyAmount` of `outputToken` was bought.
|
||||
/// @param params Batch sell parameters.
|
||||
/// @param minBuyAmount The minimum amount of `outputToken` that
|
||||
/// must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of `outputToken` bought.
|
||||
function _multiplexBatchSell(
|
||||
function _multiplexBatchSellPrivate(
|
||||
BatchSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) private returns (uint256 boughtAmount) {
|
||||
@@ -226,13 +245,14 @@ contract MultiplexFeature is
|
||||
// WETH is now held by this contract,
|
||||
// so `useSelfBalance` is true.
|
||||
return
|
||||
_multiplexMultiHopSell(
|
||||
_multiplexMultiHopSellPrivate(
|
||||
MultiHopSellParams({
|
||||
tokens: tokens,
|
||||
sellAmount: msg.value,
|
||||
calls: calls,
|
||||
useSelfBalance: true,
|
||||
recipient: msg.sender
|
||||
recipient: msg.sender,
|
||||
msgSender: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
@@ -262,13 +282,14 @@ contract MultiplexFeature is
|
||||
);
|
||||
// The `recipient of the WETH is set to this contract, since
|
||||
// we must unwrap the WETH and transfer the resulting ETH.
|
||||
boughtAmount = _multiplexMultiHopSell(
|
||||
boughtAmount = _multiplexMultiHopSellPrivate(
|
||||
MultiHopSellParams({
|
||||
tokens: tokens,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: address(this)
|
||||
recipient: address(this),
|
||||
msgSender: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
@@ -297,25 +318,38 @@ contract MultiplexFeature is
|
||||
uint256 minBuyAmount
|
||||
) public override returns (uint256 boughtAmount) {
|
||||
return
|
||||
_multiplexMultiHopSell(
|
||||
_multiplexMultiHopSellPrivate(
|
||||
MultiHopSellParams({
|
||||
tokens: tokens,
|
||||
sellAmount: sellAmount,
|
||||
calls: calls,
|
||||
useSelfBalance: false,
|
||||
recipient: msg.sender
|
||||
recipient: msg.sender,
|
||||
msgSender: msg.sender
|
||||
}),
|
||||
minBuyAmount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Executes a multi-hop sell. Internal variant.
|
||||
/// @param params Multi-hop sell parameters.
|
||||
/// @param minBuyAmount The minimum amount of output tokens that
|
||||
/// must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of output tokens bought.
|
||||
function _multiplexMultiHopSell(
|
||||
MultiHopSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) public override onlySelf returns (uint256 boughtAmount) {
|
||||
return _multiplexMultiHopSellPrivate(params, minBuyAmount);
|
||||
}
|
||||
|
||||
/// @dev Executes a multi-hop sell and checks that at least
|
||||
/// `minBuyAmount` of output tokens were bought.
|
||||
/// @param params Multi-hop sell parameters.
|
||||
/// @param minBuyAmount The minimum amount of output tokens that
|
||||
/// must be bought for this function to not revert.
|
||||
/// @return boughtAmount The amount of output tokens bought.
|
||||
function _multiplexMultiHopSell(
|
||||
function _multiplexMultiHopSellPrivate(
|
||||
MultiHopSellParams memory params,
|
||||
uint256 minBuyAmount
|
||||
) private returns (uint256 boughtAmount) {
|
||||
@@ -387,14 +421,14 @@ contract MultiplexFeature is
|
||||
// amount of the multi-hop fill.
|
||||
state.outputTokenAmount = params.sellAmount;
|
||||
// The first call may expect the input tokens to be held by
|
||||
// `msg.sender`, `address(this)`, or some other address.
|
||||
// `msgSender`, `address(this)`, or some other address.
|
||||
// Compute the expected address and transfer the input tokens
|
||||
// there if necessary.
|
||||
state.from = _computeHopTarget(params, 0);
|
||||
// If the input tokens are currently held by `msg.sender` but
|
||||
// If the input tokens are currently held by `msgSender` but
|
||||
// the first hop expects them elsewhere, perform a `transferFrom`.
|
||||
if (!params.useSelfBalance && state.from != msg.sender) {
|
||||
_transferERC20TokensFrom(IERC20TokenV06(params.tokens[0]), msg.sender, state.from, params.sellAmount);
|
||||
if (!params.useSelfBalance && state.from != params.msgSender) {
|
||||
_transferERC20TokensFrom(IERC20TokenV06(params.tokens[0]), params.msgSender, state.from, params.sellAmount);
|
||||
}
|
||||
// If the input tokens are currently held by `address(this)` but
|
||||
// the first hop expects them elsewhere, perform a `transfer`.
|
||||
@@ -411,7 +445,7 @@ contract MultiplexFeature is
|
||||
if (subcall.id == MultiplexSubcall.UniswapV2) {
|
||||
_multiHopSellUniswapV2(state, params, subcall.data);
|
||||
} else if (subcall.id == MultiplexSubcall.UniswapV3) {
|
||||
_multiHopSellUniswapV3(state, subcall.data);
|
||||
_multiHopSellUniswapV3(state, params, subcall.data);
|
||||
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
|
||||
_multiHopSellLiquidityProvider(state, params, subcall.data);
|
||||
} else if (subcall.id == MultiplexSubcall.BatchSell) {
|
||||
@@ -443,6 +477,8 @@ contract MultiplexFeature is
|
||||
// Likewise, the recipient of the multi-hop sell is
|
||||
// equal to the recipient of its containing batch sell.
|
||||
multiHopParams.recipient = params.recipient;
|
||||
// The msgSender is the same too.
|
||||
multiHopParams.msgSender = params.msgSender;
|
||||
// Execute the nested multi-hop sell.
|
||||
uint256 outputTokenAmount = _executeMultiHopSell(multiHopParams).outputTokenAmount;
|
||||
// Increment the sold and bought amounts.
|
||||
@@ -469,7 +505,7 @@ contract MultiplexFeature is
|
||||
// If the nested batch sell is the first hop
|
||||
// and `useSelfBalance` for the containing multi-
|
||||
// hop sell is false, the nested batch sell should
|
||||
// pull tokens from `msg.sender` (so `batchSellParams.useSelfBalance`
|
||||
// pull tokens from `msgSender` (so `batchSellParams.useSelfBalance`
|
||||
// should be false). Otherwise `batchSellParams.useSelfBalance`
|
||||
// should be true.
|
||||
batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance;
|
||||
@@ -477,6 +513,8 @@ contract MultiplexFeature is
|
||||
// that should receive the output tokens of the
|
||||
// batch sell.
|
||||
batchSellParams.recipient = state.to;
|
||||
// msgSender shound be the same too.
|
||||
batchSellParams.msgSender = params.msgSender;
|
||||
// Execute the nested batch sell.
|
||||
state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount;
|
||||
}
|
||||
@@ -509,25 +547,25 @@ contract MultiplexFeature is
|
||||
// UniswapV3 uses a callback to pull in the tokens being
|
||||
// sold to it. The callback implemented in `UniswapV3Feature`
|
||||
// can either:
|
||||
// - call `transferFrom` to move tokens from `msg.sender` to the
|
||||
// - call `transferFrom` to move tokens from `msgSender` to the
|
||||
// UniswapV3 pool, or
|
||||
// - call `transfer` to move tokens from `address(this)` to the
|
||||
// UniswapV3 pool.
|
||||
// A nested batch sell is similar, in that it can either:
|
||||
// - use tokens from `msg.sender`, or
|
||||
// - use tokens from `msgSender`, or
|
||||
// - use tokens held by `address(this)`.
|
||||
|
||||
// Suppose UniswapV3/BatchSell is the first call in the multi-hop
|
||||
// path. The input tokens are either held by `msg.sender`,
|
||||
// path. The input tokens are either held by `msgSender`,
|
||||
// or in the case of `multiplexMultiHopSellEthForToken` WETH is
|
||||
// held by `address(this)`. The target is set accordingly.
|
||||
|
||||
// If this is _not_ the first call in the multi-hop path, we
|
||||
// are dealing with an "intermediate" token in the multi-hop path,
|
||||
// which `msg.sender` may not have an allowance set for. Thus
|
||||
// which `msgSender` may not have an allowance set for. Thus
|
||||
// target must be set to `address(this)` for `i > 0`.
|
||||
if (i == 0 && !params.useSelfBalance) {
|
||||
target = msg.sender;
|
||||
target = params.msgSender;
|
||||
} else {
|
||||
target = address(this);
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ abstract contract MultiplexLiquidityProvider is FixinCommon, FixinTokenSpender {
|
||||
// held by `address(this)`.
|
||||
_transferERC20Tokens(params.inputToken, provider, sellAmount);
|
||||
} else {
|
||||
// Otherwise, transfer the input tokens from `msg.sender`.
|
||||
_transferERC20TokensFrom(params.inputToken, msg.sender, provider, sellAmount);
|
||||
// Otherwise, transfer the input tokens from `msgSender`.
|
||||
_transferERC20TokensFrom(params.inputToken, params.msgSender, provider, sellAmount);
|
||||
}
|
||||
// Cache the recipient's balance of the output token.
|
||||
uint256 balanceBefore = params.outputToken.balanceOf(params.recipient);
|
||||
|
||||
@@ -55,7 +55,7 @@ abstract contract MultiplexOtc is FixinEIP712 {
|
||||
order,
|
||||
signature,
|
||||
sellAmount.safeDowncastToUint128(),
|
||||
msg.sender,
|
||||
params.msgSender,
|
||||
params.useSelfBalance,
|
||||
params.recipient
|
||||
)
|
||||
|
||||
@@ -54,7 +54,7 @@ abstract contract MultiplexRfq is FixinEIP712 {
|
||||
order,
|
||||
signature,
|
||||
sellAmount.safeDowncastToUint128(),
|
||||
msg.sender,
|
||||
params.msgSender,
|
||||
params.useSelfBalance,
|
||||
params.recipient
|
||||
)
|
||||
|
||||
@@ -30,8 +30,8 @@ abstract contract MultiplexTransformERC20 {
|
||||
) internal {
|
||||
ITransformERC20Feature.TransformERC20Args memory args;
|
||||
// We want the TransformedERC20 event to have
|
||||
// `msg.sender` as the taker.
|
||||
args.taker = msg.sender;
|
||||
// `msgSender` as the taker.
|
||||
args.taker = payable(params.msgSender);
|
||||
args.inputToken = params.inputToken;
|
||||
args.outputToken = params.outputToken;
|
||||
args.inputTokenAmount = sellAmount;
|
||||
|
||||
@@ -77,7 +77,7 @@ abstract contract MultiplexUniswapV2 is FixinCommon, FixinTokenSpender {
|
||||
if (params.useSelfBalance) {
|
||||
_transferERC20Tokens(IERC20TokenV06(tokens[0]), firstPairAddress, sellAmount);
|
||||
} else {
|
||||
_transferERC20TokensFrom(IERC20TokenV06(tokens[0]), msg.sender, firstPairAddress, sellAmount);
|
||||
_transferERC20TokensFrom(IERC20TokenV06(tokens[0]), params.msgSender, firstPairAddress, sellAmount);
|
||||
}
|
||||
// Execute the Uniswap/Sushiswap trade.
|
||||
return _sellToUniswapV2(tokens, sellAmount, isSushi, firstPairAddress, params.recipient);
|
||||
|
||||
@@ -46,16 +46,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Otherwise, we self-delegatecall the normal variant
|
||||
// `sellTokenForTokenToUniswapV3`, which pulls the input token
|
||||
// from `msg.sender`.
|
||||
// Otherwise, we self-delegatecall `_sellTokenForTokenToUniswapV3`,
|
||||
// which pulls the input token from a specified `payer`.
|
||||
(success, resultData) = address(this).delegatecall(
|
||||
abi.encodeWithSelector(
|
||||
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
|
||||
IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
|
||||
wrappedCallData,
|
||||
sellAmount,
|
||||
0,
|
||||
params.recipient
|
||||
params.recipient,
|
||||
params.msgSender
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -70,6 +70,7 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
|
||||
|
||||
function _multiHopSellUniswapV3(
|
||||
IMultiplexFeature.MultiHopSellState memory state,
|
||||
IMultiplexFeature.MultiHopSellParams memory params,
|
||||
bytes memory wrappedCallData
|
||||
) internal {
|
||||
bool success;
|
||||
@@ -88,16 +89,16 @@ abstract contract MultiplexUniswapV3 is FixinTokenSpender {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Otherwise, we self-delegatecall the normal variant
|
||||
// `sellTokenForTokenToUniswapV3`, which pulls the input token
|
||||
// from `msg.sender`.
|
||||
// Otherwise, we self-delegatecall `_sellTokenForTokenToUniswapV3`,
|
||||
// which pulls the input token from `msgSender`.
|
||||
(success, resultData) = address(this).delegatecall(
|
||||
abi.encodeWithSelector(
|
||||
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
|
||||
IUniswapV3Feature._sellTokenForTokenToUniswapV3.selector,
|
||||
wrappedCallData,
|
||||
state.outputTokenAmount,
|
||||
0,
|
||||
state.to
|
||||
state.to,
|
||||
params.msgSender
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ import "../errors/LibTransformERC20RichErrors.sol";
|
||||
import "./Transformer.sol";
|
||||
import "./LibERC20Transformer.sol";
|
||||
|
||||
/// @dev A transformer that transfers token surplus to arbitrary addresses.
|
||||
contract TradeSurplusTransformer is Transformer {
|
||||
/// @dev A transformer that transfers tokens to arbitrary addresses.
|
||||
contract PositiveSlippageFeeTransformer is Transformer {
|
||||
using LibRichErrorsV06 for bytes;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibERC20Transformer for IERC20TokenV06;
|
||||
@@ -36,9 +36,9 @@
|
||||
"typechain": "typechain --target=ethers-v5 --out-dir='typechain-wrappers' './foundry-artifacts/**/*.json'"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,TradeSurplusTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter",
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|ArbitrumBridgeAdapter|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinSolidly|MixinSynthetix|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|SimpleFunctionRegistryFeature|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TradeSurplusTransformer|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
"abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|ArbitrumBridgeAdapter|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinSolidly|MixinSynthetix|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -39,8 +39,8 @@ import * as OtcOrdersFeature from '../generated-artifacts/OtcOrdersFeature.json'
|
||||
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
|
||||
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
|
||||
import * as PolygonBridgeAdapter from '../generated-artifacts/PolygonBridgeAdapter.json';
|
||||
import * as PositiveSlippageFeeTransformer from '../generated-artifacts/PositiveSlippageFeeTransformer.json';
|
||||
import * as SimpleFunctionRegistryFeature from '../generated-artifacts/SimpleFunctionRegistryFeature.json';
|
||||
import * as TradeSurplusTransformer from '../generated-artifacts/TradeSurplusTransformer.json';
|
||||
import * as TransformERC20Feature from '../generated-artifacts/TransformERC20Feature.json';
|
||||
import * as WethTransformer from '../generated-artifacts/WethTransformer.json';
|
||||
import * as ZeroEx from '../generated-artifacts/ZeroEx.json';
|
||||
@@ -56,7 +56,7 @@ export const artifacts = {
|
||||
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
|
||||
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
|
||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||
TradeSurplusTransformer: TradeSurplusTransformer as ContractArtifact,
|
||||
PositiveSlippageFeeTransformer: PositiveSlippageFeeTransformer as ContractArtifact,
|
||||
WethTransformer: WethTransformer as ContractArtifact,
|
||||
OwnableFeature: OwnableFeature as ContractArtifact,
|
||||
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
|
||||
|
||||
@@ -52,7 +52,7 @@ export {
|
||||
OptimismBridgeAdapterContract,
|
||||
PayTakerTransformerContract,
|
||||
PolygonBridgeAdapterContract,
|
||||
TradeSurplusTransformerContract,
|
||||
PositiveSlippageFeeTransformerContract,
|
||||
TransformERC20FeatureContract,
|
||||
WethTransformerContract,
|
||||
ZeroExContract,
|
||||
|
||||
@@ -37,8 +37,8 @@ export * from '../generated-wrappers/otc_orders_feature';
|
||||
export * from '../generated-wrappers/ownable_feature';
|
||||
export * from '../generated-wrappers/pay_taker_transformer';
|
||||
export * from '../generated-wrappers/polygon_bridge_adapter';
|
||||
export * from '../generated-wrappers/positive_slippage_fee_transformer';
|
||||
export * from '../generated-wrappers/simple_function_registry_feature';
|
||||
export * from '../generated-wrappers/trade_surplus_transformer';
|
||||
export * from '../generated-wrappers/transform_erc20_feature';
|
||||
export * from '../generated-wrappers/weth_transformer';
|
||||
export * from '../generated-wrappers/zero_ex';
|
||||
|
||||
@@ -151,6 +151,7 @@ import * as PancakeSwapFeature from '../test/generated-artifacts/PancakeSwapFeat
|
||||
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
|
||||
import * as PermissionlessTransformerDeployer from '../test/generated-artifacts/PermissionlessTransformerDeployer.json';
|
||||
import * as PolygonBridgeAdapter from '../test/generated-artifacts/PolygonBridgeAdapter.json';
|
||||
import * as PositiveSlippageFeeTransformer from '../test/generated-artifacts/PositiveSlippageFeeTransformer.json';
|
||||
import * as SimpleFunctionRegistryFeature from '../test/generated-artifacts/SimpleFunctionRegistryFeature.json';
|
||||
import * as TestCurve from '../test/generated-artifacts/TestCurve.json';
|
||||
import * as TestDelegateCaller from '../test/generated-artifacts/TestDelegateCaller.json';
|
||||
@@ -197,7 +198,6 @@ import * as TestUniswapV3Feature from '../test/generated-artifacts/TestUniswapV3
|
||||
import * as TestUniswapV3Pool from '../test/generated-artifacts/TestUniswapV3Pool.json';
|
||||
import * as TestWeth from '../test/generated-artifacts/TestWeth.json';
|
||||
import * as TestWethTransformerHost from '../test/generated-artifacts/TestWethTransformerHost.json';
|
||||
import * as TradeSurplusTransformer from '../test/generated-artifacts/TradeSurplusTransformer.json';
|
||||
import * as Transformer from '../test/generated-artifacts/Transformer.json';
|
||||
import * as TransformERC20Feature from '../test/generated-artifacts/TransformERC20Feature.json';
|
||||
import * as TransformerDeployer from '../test/generated-artifacts/TransformerDeployer.json';
|
||||
@@ -311,7 +311,7 @@ export const artifacts = {
|
||||
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
||||
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
|
||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||
TradeSurplusTransformer: TradeSurplusTransformer as ContractArtifact,
|
||||
PositiveSlippageFeeTransformer: PositiveSlippageFeeTransformer as ContractArtifact,
|
||||
Transformer: Transformer as ContractArtifact,
|
||||
WethTransformer: WethTransformer as ContractArtifact,
|
||||
AbstractBridgeAdapter: AbstractBridgeAdapter as ContractArtifact,
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { encodeAffiliateFeeTransformerData, ETH_TOKEN_ADDRESS } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import {
|
||||
AffiliateFeeTransformerContract,
|
||||
TestMintableERC20TokenContract,
|
||||
TestTransformerHostContract,
|
||||
} from '../wrappers';
|
||||
|
||||
const { MAX_UINT256, ZERO_AMOUNT } = constants;
|
||||
|
||||
blockchainTests.resets('AffiliateFeeTransformer', env => {
|
||||
const recipients = new Array(2).fill(0).map(() => randomAddress());
|
||||
let caller: string;
|
||||
let token: TestMintableERC20TokenContract;
|
||||
let transformer: AffiliateFeeTransformerContract;
|
||||
let host: TestTransformerHostContract;
|
||||
|
||||
before(async () => {
|
||||
[caller] = await env.getAccountAddressesAsync();
|
||||
token = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMintableERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
transformer = await AffiliateFeeTransformerContract.deployFrom0xArtifactAsync(
|
||||
artifacts.AffiliateFeeTransformer,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
host = await TestTransformerHostContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestTransformerHost,
|
||||
env.provider,
|
||||
{ ...env.txDefaults, from: caller },
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
interface Balances {
|
||||
ethBalance: BigNumber;
|
||||
tokenBalance: BigNumber;
|
||||
}
|
||||
|
||||
const ZERO_BALANCES = {
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
tokenBalance: ZERO_AMOUNT,
|
||||
};
|
||||
|
||||
async function getBalancesAsync(owner: string): Promise<Balances> {
|
||||
return {
|
||||
ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(owner),
|
||||
tokenBalance: await token.balanceOf(owner).callAsync(),
|
||||
};
|
||||
}
|
||||
|
||||
async function mintHostTokensAsync(amount: BigNumber): Promise<void> {
|
||||
await token.mint(host.address, amount).awaitTransactionSuccessAsync();
|
||||
}
|
||||
|
||||
async function sendEtherAsync(to: string, amount: BigNumber): Promise<void> {
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
...env.txDefaults,
|
||||
to,
|
||||
from: caller,
|
||||
value: amount,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
it('can transfer a token and ETH', async () => {
|
||||
const amounts = recipients.map(() => getRandomInteger(1, '1e18'));
|
||||
const tokens = [token.address, ETH_TOKEN_ADDRESS];
|
||||
const data = encodeAffiliateFeeTransformerData({
|
||||
fees: recipients.map((r, i) => ({
|
||||
token: tokens[i],
|
||||
amount: amounts[i],
|
||||
recipient: r,
|
||||
})),
|
||||
});
|
||||
await mintHostTokensAsync(amounts[0]);
|
||||
await sendEtherAsync(host.address, amounts[1]);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, {
|
||||
data,
|
||||
sender: randomAddress(),
|
||||
recipient: randomAddress(),
|
||||
})
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||
expect(await getBalancesAsync(recipients[0])).to.deep.eq({
|
||||
tokenBalance: amounts[0],
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
});
|
||||
expect(await getBalancesAsync(recipients[1])).to.deep.eq({
|
||||
tokenBalance: ZERO_AMOUNT,
|
||||
ethBalance: amounts[1],
|
||||
});
|
||||
});
|
||||
|
||||
it('can transfer all of a token and ETH', async () => {
|
||||
const amounts = recipients.map(() => getRandomInteger(1, '1e18'));
|
||||
const tokens = [token.address, ETH_TOKEN_ADDRESS];
|
||||
const data = encodeAffiliateFeeTransformerData({
|
||||
fees: recipients.map((r, i) => ({
|
||||
token: tokens[i],
|
||||
amount: MAX_UINT256,
|
||||
recipient: r,
|
||||
})),
|
||||
});
|
||||
await mintHostTokensAsync(amounts[0]);
|
||||
await sendEtherAsync(host.address, amounts[1]);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, {
|
||||
data,
|
||||
sender: randomAddress(),
|
||||
recipient: randomAddress(),
|
||||
})
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||
expect(await getBalancesAsync(recipients[0])).to.deep.eq({
|
||||
tokenBalance: amounts[0],
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
});
|
||||
expect(await getBalancesAsync(recipients[1])).to.deep.eq({
|
||||
tokenBalance: ZERO_AMOUNT,
|
||||
ethBalance: amounts[1],
|
||||
});
|
||||
});
|
||||
|
||||
it('can transfer less than the balance of a token and ETH', async () => {
|
||||
const amounts = recipients.map(() => getRandomInteger(1, '1e18'));
|
||||
const tokens = [token.address, ETH_TOKEN_ADDRESS];
|
||||
const data = encodeAffiliateFeeTransformerData({
|
||||
fees: recipients.map((r, i) => ({
|
||||
token: tokens[i],
|
||||
amount: amounts[i].minus(1),
|
||||
recipient: r,
|
||||
})),
|
||||
});
|
||||
await mintHostTokensAsync(amounts[0]);
|
||||
await sendEtherAsync(host.address, amounts[1]);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, {
|
||||
data,
|
||||
sender: randomAddress(),
|
||||
recipient: randomAddress(),
|
||||
})
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq({
|
||||
tokenBalance: new BigNumber(1),
|
||||
ethBalance: new BigNumber(1),
|
||||
});
|
||||
expect(await getBalancesAsync(recipients[0])).to.deep.eq({
|
||||
tokenBalance: amounts[0].minus(1),
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
});
|
||||
expect(await getBalancesAsync(recipients[1])).to.deep.eq({
|
||||
tokenBalance: ZERO_AMOUNT,
|
||||
ethBalance: amounts[1].minus(1),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { encodePositiveSlippageFeeTransformerData } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import {
|
||||
PositiveSlippageFeeTransformerContract,
|
||||
TestMintableERC20TokenContract,
|
||||
TestTransformerHostContract,
|
||||
} from '../wrappers';
|
||||
|
||||
const { ZERO_AMOUNT } = constants;
|
||||
|
||||
blockchainTests.resets('PositiveSlippageFeeTransformer', env => {
|
||||
const recipient = randomAddress();
|
||||
let caller: string;
|
||||
let token: TestMintableERC20TokenContract;
|
||||
let transformer: PositiveSlippageFeeTransformerContract;
|
||||
let host: TestTransformerHostContract;
|
||||
|
||||
before(async () => {
|
||||
[caller] = await env.getAccountAddressesAsync();
|
||||
token = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMintableERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
transformer = await PositiveSlippageFeeTransformerContract.deployFrom0xArtifactAsync(
|
||||
artifacts.PositiveSlippageFeeTransformer,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
host = await TestTransformerHostContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestTransformerHost,
|
||||
env.provider,
|
||||
{ ...env.txDefaults, from: caller },
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
interface Balances {
|
||||
ethBalance: BigNumber;
|
||||
tokenBalance: BigNumber;
|
||||
}
|
||||
|
||||
async function getBalancesAsync(owner: string): Promise<Balances> {
|
||||
return {
|
||||
ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(owner),
|
||||
tokenBalance: await token.balanceOf(owner).callAsync(),
|
||||
};
|
||||
}
|
||||
|
||||
async function mintHostTokensAsync(amount: BigNumber): Promise<void> {
|
||||
await token.mint(host.address, amount).awaitTransactionSuccessAsync();
|
||||
}
|
||||
|
||||
it('does not transfer positive slippage fees when bestCaseAmount is equal to amount', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const data = encodePositiveSlippageFeeTransformerData({
|
||||
token: token.address,
|
||||
bestCaseAmount: amount,
|
||||
recipient,
|
||||
});
|
||||
await mintHostTokensAsync(amount);
|
||||
const beforeBalanceHost = await getBalancesAsync(host.address);
|
||||
const beforeBalanceRecipient = await getBalancesAsync(recipient);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, {
|
||||
data,
|
||||
sender: randomAddress(),
|
||||
recipient: randomAddress(),
|
||||
})
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq(beforeBalanceHost);
|
||||
expect(await getBalancesAsync(recipient)).to.deep.eq(beforeBalanceRecipient);
|
||||
});
|
||||
|
||||
it('does not transfer positive slippage fees when bestCaseAmount is higher than amount', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const bestCaseAmount = amount.times(1.1).decimalPlaces(0, BigNumber.ROUND_FLOOR);
|
||||
const data = encodePositiveSlippageFeeTransformerData({
|
||||
token: token.address,
|
||||
bestCaseAmount,
|
||||
recipient,
|
||||
});
|
||||
await mintHostTokensAsync(amount);
|
||||
const beforeBalanceHost = await getBalancesAsync(host.address);
|
||||
const beforeBalanceRecipient = await getBalancesAsync(recipient);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, {
|
||||
data,
|
||||
sender: randomAddress(),
|
||||
recipient: randomAddress(),
|
||||
})
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq(beforeBalanceHost);
|
||||
expect(await getBalancesAsync(recipient)).to.deep.eq(beforeBalanceRecipient);
|
||||
});
|
||||
|
||||
it('send positive slippage fee to recipient when bestCaseAmount is lower than amount', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const bestCaseAmount = amount.times(0.95).decimalPlaces(0, BigNumber.ROUND_FLOOR);
|
||||
const data = encodePositiveSlippageFeeTransformerData({
|
||||
token: token.address,
|
||||
bestCaseAmount,
|
||||
recipient,
|
||||
});
|
||||
await mintHostTokensAsync(amount);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, {
|
||||
data,
|
||||
sender: randomAddress(),
|
||||
recipient: randomAddress(),
|
||||
})
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq({
|
||||
tokenBalance: bestCaseAmount,
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
});
|
||||
expect(await getBalancesAsync(recipient)).to.deep.eq({
|
||||
tokenBalance: amount.minus(bestCaseAmount), // positive slippage
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -149,6 +149,7 @@ export * from '../test/generated-wrappers/pancake_swap_feature';
|
||||
export * from '../test/generated-wrappers/pay_taker_transformer';
|
||||
export * from '../test/generated-wrappers/permissionless_transformer_deployer';
|
||||
export * from '../test/generated-wrappers/polygon_bridge_adapter';
|
||||
export * from '../test/generated-wrappers/positive_slippage_fee_transformer';
|
||||
export * from '../test/generated-wrappers/simple_function_registry_feature';
|
||||
export * from '../test/generated-wrappers/test_curve';
|
||||
export * from '../test/generated-wrappers/test_delegate_caller';
|
||||
@@ -195,7 +196,6 @@ export * from '../test/generated-wrappers/test_uniswap_v3_feature';
|
||||
export * from '../test/generated-wrappers/test_uniswap_v3_pool';
|
||||
export * from '../test/generated-wrappers/test_weth';
|
||||
export * from '../test/generated-wrappers/test_weth_transformer_host';
|
||||
export * from '../test/generated-wrappers/trade_surplus_transformer';
|
||||
export * from '../test/generated-wrappers/transform_erc20_feature';
|
||||
export * from '../test/generated-wrappers/transformer';
|
||||
export * from '../test/generated-wrappers/transformer_deployer';
|
||||
|
||||
@@ -29,7 +29,7 @@ import "src/transformers/bridges/BridgeProtocols.sol";
|
||||
import "src/transformers/bridges/EthereumBridgeAdapter.sol";
|
||||
import "src/IZeroEx.sol";
|
||||
|
||||
contract WrapEthTest is Test, ForkUtils, TestUtils {
|
||||
contract WrapEth is Test, ForkUtils, TestUtils {
|
||||
DeployZeroEx.ZeroExDeployed zeroExDeployed;
|
||||
|
||||
function setUp() public {
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright 2023 ZeroEx Intl.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/WETH9V06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/MintableERC20TokenV06.sol";
|
||||
|
||||
import "utils/BaseTest.sol";
|
||||
import "../../contracts/src/transformers/AffiliateFeeTransformer.sol";
|
||||
import "../../contracts/src/transformers/IERC20Transformer.sol";
|
||||
|
||||
contract AffiliateFeeTransformerTest is BaseTest {
|
||||
address public owner = account1;
|
||||
address public feeRecipient = account2;
|
||||
WETH9V06 weth = new WETH9V06();
|
||||
IERC20TokenV06 token1 = IERC20TokenV06(address(weth));
|
||||
|
||||
AffiliateFeeTransformer target = new AffiliateFeeTransformer();
|
||||
|
||||
function setUp() public {
|
||||
vm.deal(address(this), 1e19);
|
||||
weth.deposit{value: 10}();
|
||||
}
|
||||
|
||||
function test_affiliateFee() public {
|
||||
// Send positive slippage to contract which executes AffiliateFeeTransformer
|
||||
weth.transfer(address(target), 10);
|
||||
uint256 affiliateFeeAmount = 1;
|
||||
|
||||
AffiliateFeeTransformer.TokenFee[] memory fees = new AffiliateFeeTransformer.TokenFee[](1);
|
||||
fees[0] = AffiliateFeeTransformer.TokenFee({
|
||||
token: IERC20TokenV06(token1),
|
||||
amount: affiliateFeeAmount,
|
||||
recipient: payable(feeRecipient)
|
||||
});
|
||||
|
||||
bytes4 result = target.transform(
|
||||
IERC20Transformer.TransformContext({
|
||||
sender: payable(address(this)),
|
||||
recipient: payable(address(this)),
|
||||
data: abi.encode(fees)
|
||||
})
|
||||
);
|
||||
assertEq(token1.balanceOf(feeRecipient), affiliateFeeAmount);
|
||||
assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS);
|
||||
}
|
||||
|
||||
function test_affiliateFee_entireBalance() public {
|
||||
// Send positive slippage to contract which executes AffiliateFeeTransformer
|
||||
weth.transfer(address(target), 10);
|
||||
|
||||
AffiliateFeeTransformer.TokenFee[] memory fees = new AffiliateFeeTransformer.TokenFee[](1);
|
||||
fees[0] = AffiliateFeeTransformer.TokenFee({
|
||||
token: IERC20TokenV06(token1),
|
||||
amount: uint256(-1),
|
||||
recipient: payable(feeRecipient)
|
||||
});
|
||||
|
||||
bytes4 result = target.transform(
|
||||
IERC20Transformer.TransformContext({
|
||||
sender: payable(address(this)),
|
||||
recipient: payable(address(this)),
|
||||
data: abi.encode(fees)
|
||||
})
|
||||
);
|
||||
assertEq(token1.balanceOf(feeRecipient), 10);
|
||||
assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS);
|
||||
}
|
||||
|
||||
function test_affiliateFee_multipleFees() public {
|
||||
// Send positive slippage to contract which executes AffiliateFeeTransformer
|
||||
weth.transfer(address(target), 10);
|
||||
|
||||
AffiliateFeeTransformer.TokenFee[] memory fees = new AffiliateFeeTransformer.TokenFee[](2);
|
||||
fees[0] = AffiliateFeeTransformer.TokenFee({
|
||||
token: IERC20TokenV06(token1),
|
||||
amount: uint256(1),
|
||||
recipient: payable(feeRecipient)
|
||||
});
|
||||
|
||||
fees[1] = AffiliateFeeTransformer.TokenFee({
|
||||
token: IERC20TokenV06(token1),
|
||||
amount: uint256(1),
|
||||
recipient: payable(feeRecipient)
|
||||
});
|
||||
|
||||
bytes4 result = target.transform(
|
||||
IERC20Transformer.TransformContext({
|
||||
sender: payable(address(this)),
|
||||
recipient: payable(address(this)),
|
||||
data: abi.encode(fees)
|
||||
})
|
||||
);
|
||||
assertEq(token1.balanceOf(feeRecipient), 2);
|
||||
assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS);
|
||||
}
|
||||
|
||||
function test_affiliateFee_ethFee() public {
|
||||
// Send positive slippage ETH to contract which executes AffiliateFeeTransformer
|
||||
vm.deal(address(target), 1);
|
||||
uint256 ethBalanceBefore = feeRecipient.balance;
|
||||
|
||||
AffiliateFeeTransformer.TokenFee[] memory fees = new AffiliateFeeTransformer.TokenFee[](1);
|
||||
fees[0] = AffiliateFeeTransformer.TokenFee({
|
||||
token: IERC20TokenV06(LibERC20Transformer.ETH_TOKEN_ADDRESS),
|
||||
amount: uint256(1),
|
||||
recipient: payable(feeRecipient)
|
||||
});
|
||||
|
||||
bytes4 result = target.transform(
|
||||
IERC20Transformer.TransformContext({
|
||||
sender: payable(address(this)),
|
||||
recipient: payable(address(this)),
|
||||
data: abi.encode(fees)
|
||||
})
|
||||
);
|
||||
assertEq(feeRecipient.balance, ethBalanceBefore + 1);
|
||||
assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS);
|
||||
}
|
||||
|
||||
function test_affiliateFee_multipleFeesEthToken() public {
|
||||
// Send positive slippage to contract which executes AffiliateFeeTransformer
|
||||
weth.transfer(address(target), 10);
|
||||
vm.deal(address(target), 10);
|
||||
|
||||
uint256 ethBalanceBefore = feeRecipient.balance;
|
||||
|
||||
AffiliateFeeTransformer.TokenFee[] memory fees = new AffiliateFeeTransformer.TokenFee[](2);
|
||||
fees[0] = AffiliateFeeTransformer.TokenFee({
|
||||
token: IERC20TokenV06(LibERC20Transformer.ETH_TOKEN_ADDRESS),
|
||||
amount: uint256(1),
|
||||
recipient: payable(feeRecipient)
|
||||
});
|
||||
|
||||
fees[1] = AffiliateFeeTransformer.TokenFee({
|
||||
token: IERC20TokenV06(token1),
|
||||
amount: uint256(1),
|
||||
recipient: payable(feeRecipient)
|
||||
});
|
||||
|
||||
bytes4 result = target.transform(
|
||||
IERC20Transformer.TransformContext({
|
||||
sender: payable(address(this)),
|
||||
recipient: payable(address(this)),
|
||||
data: abi.encode(fees)
|
||||
})
|
||||
);
|
||||
assertEq(feeRecipient.balance, ethBalanceBefore + 1);
|
||||
assertEq(token1.balanceOf(feeRecipient), 1);
|
||||
assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS);
|
||||
}
|
||||
|
||||
function test_affiliateFee_zeroAmount() public {
|
||||
// Send positive slippage to contract which executes AffiliateFeeTransformer
|
||||
weth.transfer(address(target), 10);
|
||||
|
||||
AffiliateFeeTransformer.TokenFee[] memory fees = new AffiliateFeeTransformer.TokenFee[](1);
|
||||
fees[0] = AffiliateFeeTransformer.TokenFee({
|
||||
token: IERC20TokenV06(token1),
|
||||
amount: 0,
|
||||
recipient: payable(feeRecipient)
|
||||
});
|
||||
|
||||
bytes4 result = target.transform(
|
||||
IERC20Transformer.TransformContext({
|
||||
sender: payable(address(this)),
|
||||
recipient: payable(address(this)),
|
||||
data: abi.encode(fees)
|
||||
})
|
||||
);
|
||||
assertEq(token1.balanceOf(feeRecipient), 0);
|
||||
assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright 2023 ZeroEx Intl.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/WETH9V06.sol";
|
||||
|
||||
import "utils/BaseTest.sol";
|
||||
import "../../contracts/src/transformers/TradeSurplusTransformer.sol";
|
||||
import "../../contracts/src/transformers/IERC20Transformer.sol";
|
||||
|
||||
contract TradeSurplusTransformerTest is BaseTest {
|
||||
address public owner = account1;
|
||||
address public feeRecipient = account2;
|
||||
WETH9V06 weth = new WETH9V06();
|
||||
IERC20TokenV06 token1 = IERC20TokenV06(address(weth));
|
||||
|
||||
TradeSurplusTransformer target = new TradeSurplusTransformer();
|
||||
|
||||
function setUp() public {
|
||||
vm.deal(address(this), 1e19);
|
||||
weth.deposit{value: 10}();
|
||||
}
|
||||
|
||||
function test_tradeSurplus() public {
|
||||
// Send trade surplus to contract which executes TradeSurplusTransformer
|
||||
weth.transfer(address(target), 10);
|
||||
uint256 bestCaseAmount = 1;
|
||||
|
||||
bytes4 result = target.transform(
|
||||
IERC20Transformer.TransformContext({
|
||||
sender: payable(address(this)),
|
||||
recipient: payable(address(this)),
|
||||
data: abi.encode(
|
||||
TradeSurplusTransformer.TokenFee({
|
||||
token: IERC20TokenV06(token1),
|
||||
bestCaseAmount: bestCaseAmount,
|
||||
recipient: payable(feeRecipient)
|
||||
})
|
||||
)
|
||||
})
|
||||
);
|
||||
assertEq(token1.balanceOf(feeRecipient), 9);
|
||||
assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS);
|
||||
}
|
||||
|
||||
function test_tradeSurplus_bestCaseEqualsAmount() public {
|
||||
uint256 bestCaseAmount = 10;
|
||||
weth.transfer(address(target), 10);
|
||||
|
||||
bytes4 result = target.transform(
|
||||
IERC20Transformer.TransformContext({
|
||||
sender: payable(address(this)),
|
||||
recipient: payable(address(this)),
|
||||
data: abi.encode(
|
||||
TradeSurplusTransformer.TokenFee({
|
||||
token: IERC20TokenV06(token1),
|
||||
bestCaseAmount: bestCaseAmount,
|
||||
recipient: payable(feeRecipient)
|
||||
})
|
||||
)
|
||||
})
|
||||
);
|
||||
assertEq(token1.balanceOf(feeRecipient), 0);
|
||||
assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS);
|
||||
}
|
||||
|
||||
function test_tradeSurplus_bestCaseGreaterThanAmount() public {
|
||||
uint256 bestCaseAmount = 10;
|
||||
weth.transfer(address(target), 1);
|
||||
|
||||
bytes4 result = target.transform(
|
||||
IERC20Transformer.TransformContext({
|
||||
sender: payable(address(this)),
|
||||
recipient: payable(address(this)),
|
||||
data: abi.encode(
|
||||
TradeSurplusTransformer.TokenFee({
|
||||
token: IERC20TokenV06(token1),
|
||||
bestCaseAmount: bestCaseAmount,
|
||||
recipient: payable(feeRecipient)
|
||||
})
|
||||
)
|
||||
})
|
||||
);
|
||||
assertEq(token1.balanceOf(feeRecipient), 0);
|
||||
assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ import "src/transformers/WethTransformer.sol";
|
||||
import "src/transformers/FillQuoteTransformer.sol";
|
||||
import "src/transformers/PayTakerTransformer.sol";
|
||||
import "src/transformers/AffiliateFeeTransformer.sol";
|
||||
import "src/transformers/TradeSurplusTransformer.sol";
|
||||
import "src/transformers/PositiveSlippageFeeTransformer.sol";
|
||||
import "src/transformers/bridges/IBridgeAdapter.sol";
|
||||
import "src/transformers/bridges/EthereumBridgeAdapter.sol";
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
"generated-artifacts/OwnableFeature.json",
|
||||
"generated-artifacts/PayTakerTransformer.json",
|
||||
"generated-artifacts/PolygonBridgeAdapter.json",
|
||||
"generated-artifacts/PositiveSlippageFeeTransformer.json",
|
||||
"generated-artifacts/SimpleFunctionRegistryFeature.json",
|
||||
"generated-artifacts/TradeSurplusTransformer.json",
|
||||
"generated-artifacts/TransformERC20Feature.json",
|
||||
"generated-artifacts/WethTransformer.json",
|
||||
"generated-artifacts/ZeroEx.json",
|
||||
@@ -188,6 +188,7 @@
|
||||
"test/generated-artifacts/PayTakerTransformer.json",
|
||||
"test/generated-artifacts/PermissionlessTransformerDeployer.json",
|
||||
"test/generated-artifacts/PolygonBridgeAdapter.json",
|
||||
"test/generated-artifacts/PositiveSlippageFeeTransformer.json",
|
||||
"test/generated-artifacts/SimpleFunctionRegistryFeature.json",
|
||||
"test/generated-artifacts/TestCurve.json",
|
||||
"test/generated-artifacts/TestDelegateCaller.json",
|
||||
@@ -234,7 +235,6 @@
|
||||
"test/generated-artifacts/TestUniswapV3Pool.json",
|
||||
"test/generated-artifacts/TestWeth.json",
|
||||
"test/generated-artifacts/TestWethTransformerHost.json",
|
||||
"test/generated-artifacts/TradeSurplusTransformer.json",
|
||||
"test/generated-artifacts/TransformERC20Feature.json",
|
||||
"test/generated-artifacts/Transformer.json",
|
||||
"test/generated-artifacts/TransformerDeployer.json",
|
||||
|
||||
Reference in New Issue
Block a user