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
This commit is contained in:
Jacob Evans 2023-02-07 08:22:02 +10:00 committed by GitHub
parent c4b1f043c6
commit 80193215e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 404 additions and 295 deletions

View File

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

View File

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

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

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

View File

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