From 80193215e366623a0f5f0f26cfad91454dfe7253 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 7 Feb 2023 08:22:02 +1000 Subject: [PATCH] feat: Migrate PositiveSlippage tests [LIT-804] (#650) * feat: PositiveSlippageTransformer tests * Remove old tests * Affilite fee tests * Create mintable erc20 token for v06 * Update MintableERC20TokenV06 name --- .../src/v06/MintableERC20TokenV06.sol | 117 +++++++++++ .../affiliate_fee_transformer_test.ts | 168 ---------------- .../positive_slippage_fee_transformer_test.ts | 127 ------------ .../AffiliateFeeTransformerTest.t.sol | 187 ++++++++++++++++++ .../PositiveSlippageFeeTransformerTest.t.sol | 100 ++++++++++ 5 files changed, 404 insertions(+), 295 deletions(-) create mode 100644 contracts/erc20/contracts/src/v06/MintableERC20TokenV06.sol delete mode 100644 contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts delete mode 100644 contracts/zero-ex/test/transformers/positive_slippage_fee_transformer_test.ts create mode 100644 contracts/zero-ex/tests/transformers/AffiliateFeeTransformerTest.t.sol create mode 100644 contracts/zero-ex/tests/transformers/PositiveSlippageFeeTransformerTest.t.sol diff --git a/contracts/erc20/contracts/src/v06/MintableERC20TokenV06.sol b/contracts/erc20/contracts/src/v06/MintableERC20TokenV06.sol new file mode 100644 index 0000000000..defd9643f1 --- /dev/null +++ b/contracts/erc20/contracts/src/v06/MintableERC20TokenV06.sol @@ -0,0 +1,117 @@ +/* + + 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); + } +} diff --git a/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts b/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts deleted file mode 100644 index 249d558db0..0000000000 --- a/contracts/zero-ex/test/transformers/affiliate_fee_transformer_test.ts +++ /dev/null @@ -1,168 +0,0 @@ -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 { - return { - ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(owner), - tokenBalance: await token.balanceOf(owner).callAsync(), - }; - } - - async function mintHostTokensAsync(amount: BigNumber): Promise { - await token.mint(host.address, amount).awaitTransactionSuccessAsync(); - } - - async function sendEtherAsync(to: string, amount: BigNumber): Promise { - 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), - }); - }); -}); diff --git a/contracts/zero-ex/test/transformers/positive_slippage_fee_transformer_test.ts b/contracts/zero-ex/test/transformers/positive_slippage_fee_transformer_test.ts deleted file mode 100644 index d1aaee6fc8..0000000000 --- a/contracts/zero-ex/test/transformers/positive_slippage_fee_transformer_test.ts +++ /dev/null @@ -1,127 +0,0 @@ -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 { - return { - ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(owner), - tokenBalance: await token.balanceOf(owner).callAsync(), - }; - } - - async function mintHostTokensAsync(amount: BigNumber): Promise { - 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, - }); - }); -}); diff --git a/contracts/zero-ex/tests/transformers/AffiliateFeeTransformerTest.t.sol b/contracts/zero-ex/tests/transformers/AffiliateFeeTransformerTest.t.sol new file mode 100644 index 0000000000..ef45c91808 --- /dev/null +++ b/contracts/zero-ex/tests/transformers/AffiliateFeeTransformerTest.t.sol @@ -0,0 +1,187 @@ +// 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 "../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); + } +} diff --git a/contracts/zero-ex/tests/transformers/PositiveSlippageFeeTransformerTest.t.sol b/contracts/zero-ex/tests/transformers/PositiveSlippageFeeTransformerTest.t.sol new file mode 100644 index 0000000000..f96dcf1179 --- /dev/null +++ b/contracts/zero-ex/tests/transformers/PositiveSlippageFeeTransformerTest.t.sol @@ -0,0 +1,100 @@ +// 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 "../BaseTest.sol"; +import "../../contracts/src/transformers/PositiveSlippageFeeTransformer.sol"; +import "../../contracts/src/transformers/IERC20Transformer.sol"; + +contract PositiveSlippageFeeTransformerTest is BaseTest { + address public owner = account1; + address public feeRecipient = account2; + WETH9V06 weth = new WETH9V06(); + IERC20TokenV06 token1 = IERC20TokenV06(address(weth)); + + PositiveSlippageFeeTransformer target = new PositiveSlippageFeeTransformer(); + + function setUp() public { + vm.deal(address(this), 1e19); + weth.deposit{value: 10}(); + } + + function test_positiveSlippageFee() public { + // Send positive slippage to contract which executes PositiveSlippageFeeTransformer + 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( + PositiveSlippageFeeTransformer.TokenFee({ + token: IERC20TokenV06(token1), + bestCaseAmount: bestCaseAmount, + recipient: payable(feeRecipient) + }) + ) + }) + ); + assertEq(token1.balanceOf(feeRecipient), 9); + assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS); + } + + function test_positiveSlippageFee_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( + PositiveSlippageFeeTransformer.TokenFee({ + token: IERC20TokenV06(token1), + bestCaseAmount: bestCaseAmount, + recipient: payable(feeRecipient) + }) + ) + }) + ); + assertEq(token1.balanceOf(feeRecipient), 0); + assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS); + } + + function test_positiveSlippageFee_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( + PositiveSlippageFeeTransformer.TokenFee({ + token: IERC20TokenV06(token1), + bestCaseAmount: bestCaseAmount, + recipient: payable(feeRecipient) + }) + ) + }) + ); + assertEq(token1.balanceOf(feeRecipient), 0); + assertEq(result, LibERC20Transformer.TRANSFORMER_SUCCESS); + } +}