Compare commits

..

4 Commits

29 changed files with 734 additions and 573 deletions

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -55,7 +55,7 @@ abstract contract MultiplexOtc is FixinEIP712 {
order,
signature,
sellAmount.safeDowncastToUint128(),
msg.sender,
params.msgSender,
params.useSelfBalance,
params.recipient
)

View File

@@ -54,7 +54,7 @@ abstract contract MultiplexRfq is FixinEIP712 {
order,
signature,
sellAmount.safeDowncastToUint128(),
msg.sender,
params.msgSender,
params.useSelfBalance,
params.recipient
)

View File

@@ -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;

View File

@@ -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);

View File

@@ -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
)
);
}

View File

@@ -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;

View File

@@ -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",

View File

@@ -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,

View File

@@ -52,7 +52,7 @@ export {
OptimismBridgeAdapterContract,
PayTakerTransformerContract,
PolygonBridgeAdapterContract,
TradeSurplusTransformerContract,
PositiveSlippageFeeTransformerContract,
TransformERC20FeatureContract,
WethTransformerContract,
ZeroExContract,

View File

@@ -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';

View File

@@ -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,

View File

@@ -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),
});
});
});

View File

@@ -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,
});
});
});

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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";

View File

@@ -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",