Merge pull request #2594 from 0xProject/feat/contracts-zero-ex/AffiliateFeeTransformer
Exchange Proxy: AffiliateFeeTransformer
This commit is contained in:
commit
b39189fde3
@ -230,5 +230,4 @@ library LibTransformERC20RichErrors {
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 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-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "../errors/LibTransformERC20RichErrors.sol";
|
||||
import "./Transformer.sol";
|
||||
import "./LibERC20Transformer.sol";
|
||||
|
||||
|
||||
/// @dev A transformer that transfers tokens to arbitrary addresses.
|
||||
contract AffiliateFeeTransformer is
|
||||
Transformer
|
||||
{
|
||||
// solhint-disable no-empty-blocks
|
||||
using LibRichErrorsV06 for bytes;
|
||||
using LibSafeMathV06 for uint256;
|
||||
using LibERC20Transformer for IERC20TokenV06;
|
||||
|
||||
/// @dev Information for a single fee.
|
||||
struct TokenFee {
|
||||
// The token to transfer to `recipient`.
|
||||
IERC20TokenV06 token;
|
||||
// Amount of each `token` to transfer to `recipient`.
|
||||
// If `amount == uint256(-1)`, the entire balance of `token` will be
|
||||
// transferred.
|
||||
uint256 amount;
|
||||
// Recipient of `token`.
|
||||
address payable recipient;
|
||||
}
|
||||
|
||||
uint256 private constant MAX_UINT256 = uint256(-1);
|
||||
|
||||
/// @dev Create this contract.
|
||||
constructor()
|
||||
public
|
||||
Transformer()
|
||||
{}
|
||||
|
||||
/// @dev Transfers tokens to recipients.
|
||||
/// @param data ABI-encoded `TokenFee[]`, indicating which tokens to transfer.
|
||||
/// @return success The success bytes (`LibERC20Transformer.TRANSFORMER_SUCCESS`).
|
||||
function transform(
|
||||
bytes32, // callDataHash,
|
||||
address payable, // taker,
|
||||
bytes calldata data
|
||||
)
|
||||
external
|
||||
override
|
||||
returns (bytes4 success)
|
||||
{
|
||||
TokenFee[] memory fees = abi.decode(data, (TokenFee[]));
|
||||
|
||||
// Transfer tokens to recipients.
|
||||
for (uint256 i = 0; i < fees.length; ++i) {
|
||||
uint256 amount = fees[i].amount;
|
||||
if (amount == MAX_UINT256) {
|
||||
amount = LibERC20Transformer.getTokenBalanceOf(fees[i].token, address(this));
|
||||
}
|
||||
if (amount != 0) {
|
||||
fees[i].token.transformerTransfer(fees[i].recipient, amount);
|
||||
}
|
||||
}
|
||||
|
||||
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|IExchange|IFeature|IFlashWallet|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|PayTakerTransformer|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|IExchange|IFeature|IFlashWallet|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|PayTakerTransformer|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -120,3 +120,40 @@ export interface PayTakerTransformerData {
|
||||
export function encodePayTakerTransformerData(data: PayTakerTransformerData): string {
|
||||
return payTakerTransformerDataEncoder.encode([data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI encoder for `PayTakerTransformer.TransformData`
|
||||
*/
|
||||
export const affiliateFeeTransformerDataEncoder = AbiEncoder.create({
|
||||
name: 'data',
|
||||
type: 'tuple',
|
||||
components: [
|
||||
{
|
||||
name: 'fees',
|
||||
type: 'tuple[]',
|
||||
components: [
|
||||
{ name: 'token', type: 'address' },
|
||||
{ name: 'amount', type: 'uint256' },
|
||||
{ name: 'recipient', type: 'address' },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* `AffiliateFeeTransformer.TransformData`
|
||||
*/
|
||||
export interface AffiliateFeeTransformerData {
|
||||
fees: Array<{
|
||||
token: string;
|
||||
amount: BigNumber;
|
||||
recipient: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI-encode a `AffiliateFeeTransformer.TransformData` type.
|
||||
*/
|
||||
export function encodeAffiliateFeeTransformerData(data: AffiliateFeeTransformerData): string {
|
||||
return affiliateFeeTransformerDataEncoder.encode(data);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as AffiliateFeeTransformer from '../test/generated-artifacts/AffiliateFeeTransformer.json';
|
||||
import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json';
|
||||
import * as Bootstrap from '../test/generated-artifacts/Bootstrap.json';
|
||||
import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json';
|
||||
@ -104,6 +105,7 @@ export const artifacts = {
|
||||
LibStorage: LibStorage as ContractArtifact,
|
||||
LibTokenSpenderStorage: LibTokenSpenderStorage as ContractArtifact,
|
||||
LibTransformERC20Storage: LibTransformERC20Storage as ContractArtifact,
|
||||
AffiliateFeeTransformer: AffiliateFeeTransformer as ContractArtifact,
|
||||
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
|
||||
IERC20Transformer: IERC20Transformer as ContractArtifact,
|
||||
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
||||
|
@ -0,0 +1,157 @@
|
||||
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ETH_TOKEN_ADDRESS } from '../../src/constants';
|
||||
import { encodeAffiliateFeeTransformerData } from '../../src/transformer_data_encoders';
|
||||
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, hexUtils.random(), randomAddress(), data)
|
||||
.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, hexUtils.random(), randomAddress(), data)
|
||||
.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, hexUtils.random(), randomAddress(), data)
|
||||
.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),
|
||||
});
|
||||
});
|
||||
});
|
@ -3,6 +3,7 @@
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/affiliate_fee_transformer';
|
||||
export * from '../test/generated-wrappers/allowance_target';
|
||||
export * from '../test/generated-wrappers/bootstrap';
|
||||
export * from '../test/generated-wrappers/fill_quote_transformer';
|
||||
|
@ -16,6 +16,7 @@
|
||||
"generated-artifacts/PayTakerTransformer.json",
|
||||
"generated-artifacts/WethTransformer.json",
|
||||
"generated-artifacts/ZeroEx.json",
|
||||
"test/generated-artifacts/AffiliateFeeTransformer.json",
|
||||
"test/generated-artifacts/AllowanceTarget.json",
|
||||
"test/generated-artifacts/Bootstrap.json",
|
||||
"test/generated-artifacts/FillQuoteTransformer.json",
|
||||
|
Loading…
x
Reference in New Issue
Block a user