Compare commits

...

1 Commits

Author SHA1 Message Date
Savarn Dontamsetti
2851637ee4
DCA Orders 2023-05-25 19:04:22 -04:00
9 changed files with 720 additions and 0 deletions

View File

@ -0,0 +1,36 @@
// 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;
pragma experimental ABIEncoderV2;
import "forge-std/Test.sol";
import "forge-std/Script.sol";
import "@0x/contracts-erc20/src/IERC20Token.sol";
contract DealMe is Test, Script {
function setUp() public {}
function run() public {
// set approval
vm.startBroadcast(0x6879fAb591ed0d62537A3Cac9D7cd41218445a84);
IERC20Token(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48).approve(
0xDef1C0ded9bec7F1a1670819833240f027b25EfF,
type(uint256).max
);
vm.stopBroadcast();
}
}

View File

@ -0,0 +1,38 @@
// 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;
pragma experimental ABIEncoderV2;
import "forge-std/Test.sol";
import "forge-std/Script.sol";
import "@0x/contracts-erc20/src/IERC20Token.sol";
contract DealMe is Test, Script {
function setUp() public {}
function run() public {
vm.startBroadcast(0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503);
IERC20Token(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48).transfer(
0x6879fAb591ed0d62537A3Cac9D7cd41218445a84,
1000e6
);
(0x6879fAb591ed0d62537A3Cac9D7cd41218445a84).transfer(1 ether);
(0xb985d345c4bb8121cE2d18583b2a28e98D56d04b).transfer(1 ether);
vm.stopBroadcast();
}
}

View File

@ -0,0 +1,83 @@
// 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;
pragma experimental ABIEncoderV2;
import "../utils/ForkUtils.sol";
import "../utils/TestUtils.sol";
import "src/IZeroEx.sol";
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "src/features/TransformERC20Feature.sol";
import "src/external/TransformerDeployer.sol";
import "src/transformers/WethTransformer.sol";
import "src/transformers/FillQuoteTransformer.sol";
import "src/transformers/bridges/BridgeProtocols.sol";
import "src/features/OtcOrdersFeature.sol";
contract TraceCalldata is Test, ForkUtils, TestUtils {
using LibERC20TokenV06 for IERC20TokenV06;
function setUp() public {
_setup();
}
function test_traceZeroExCall() public {
log_string("TraceExchangeProxyCall");
for (uint256 i = 0; i < 1; i++) {
//skip fantom/avax failing test
if (i == 3 || i == 4) {
continue;
}
vm.selectFork(forkIds[chains[i]]);
log_named_string(" Selecting Fork On", chains[i]);
vm.deal(address(this), 1e18);
labelAddresses(
chains[i],
indexChainsByChain[chains[i]],
getTokens(i),
getContractAddresses(i),
getLiquiditySourceAddresses(i)
);
swapWithOtcOrder(getTokens(i), getContractAddresses(i), getLiquiditySourceAddresses(i));
}
}
/* solhint-disable function-max-lines */
function swapWithOtcOrder(
TokenAddresses memory tokens,
ContractAddresses memory addresses,
LiquiditySources memory sources
) public onlyForked {
IZERO_EX = IZeroEx(addresses.exchangeProxy);
address taker = address(0xd00d00cAca000000000000000000000000001337);
vm.label(taker, "API Quote Taker");
deal(address(tokens.USDT), taker, 100e30);
//deal(address(tokens.WrappedNativeToken), taker, 100e30);
vm.startPrank(taker);
IERC20TokenV06(tokens.USDT).approveIfBelow(address(addresses.exchangeProxy), uint256(-1));
//IERC20TokenV06(tokens.WrappedNativeToken).approveIfBelow(address(taker), uint256(-1));
//emit log_address(address(tokens.WrappedNativeToken));
IERC20TokenV06(tokens.USDT).balanceOf(taker);
IERC20TokenV06(tokens.WrappedNativeToken).balanceOf(taker);
(bool success, bytes memory result) = addresses.exchangeProxy.call(
hex"7a1eb1b9000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000003dd141a0000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000003e711b8000000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000a3f14cb81e6edb7b1249755dd20e2bfb23597fd70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d00d00caca0000000000000000000000000013370000000063f50e17000000000000000200000000000000000000000063f50da10000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001c543148f2a2124e690f19ab0042906b437f954a52b9fbef6019c3968befac860466a819bd38bb6c60c5341f65d3e596d17573c80fc78a3a837d7ed6392b66e783000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000003e711b80000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000003e6b1b23900000000000000000000000000000000000000000000000000000003e711b800000000000000000000000000af0b0000f0210d0f421f0009c72406703b50506b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d00d00caca0000000000000000000000000013370000000063f50dfb000000000000001f00000000000000000000000063f50da20000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001beb5fbe9aa7694028216b8d81e613a41a9cf8b9135729d33acf77a692c4b0123547884cb6bae49c9ac885204ae11ce6fd4fff3961c04be142933b0c1b2e99072d869584cd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007ebba8e3ba63f50e10"
);
emit log_named_bytes("", result);
vm.stopPrank();
}
}

View File

@ -0,0 +1,39 @@
// 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;
pragma experimental ABIEncoderV2;
import "forge-std/Test.sol";
import "forge-std/Script.sol";
import "src/features/DCAFeature.sol";
import "src/IZeroEx.sol";
import "src/ZeroEx.sol";
contract DeployDCAFeature is Test, Script {
ZeroEx public ZERO_EX = ZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF);
IZeroEx public IZERO_EX = IZeroEx(address(ZERO_EX));
function setUp() public {}
function run() public {
vm.startBroadcast(IZERO_EX.owner());
DCAFeature feature = new DCAFeature(address(ZERO_EX));
IZERO_EX.migrate(address(feature), abi.encodeWithSelector(DCAFeature.migrate.selector), IZERO_EX.owner());
vm.stopBroadcast();
}
}

View File

@ -0,0 +1,68 @@
// 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;
pragma experimental ABIEncoderV2;
import "forge-std/Test.sol";
import "forge-std/Script.sol";
import "src/features/DCAFeature.sol";
import "src/features/interfaces/IDCAFeature.sol";
import "src/IZeroEx.sol";
import "src/ZeroEx.sol";
contract TestDeploy is Test, Script {
ZeroEx public ZERO_EX = ZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF);
IZeroEx public IZERO_EX = IZeroEx(address(ZERO_EX));
function setUp() public {}
function run() public {
uint256 signerPrivateKey = 0xA11CE;
address signer = vm.addr(signerPrivateKey);
IDCAFeature.DCAData memory dcaData = IDCAFeature.DCAData({
buyToken: IERC20Token(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2),
sellToken: IERC20Token(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48),
sellAmount: 100,
interval: 600,
numFills: 12,
startTime: block.timestamp,
signer: payable(signer)
});
bytes32 dcahash = IDCAFeature(address(ZERO_EX)).getDCAHash(dcaData);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, dcahash);
LibSignature.Signature memory signature = LibSignature.Signature({
signatureType: LibSignature.SignatureType.EIP712,
v: v,
r: r,
s: s
});
ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](0);
bytes memory swapCalldata = abi.encodeWithSelector(
IZERO_EX.transformERC20.selector,
dcaData.sellToken,
dcaData.buyToken,
1e18,
1e18,
transformations
);
IDCAFeature(address(ZERO_EX)).fillDCATransaction(dcaData, signature, swapCalldata);
}
}

View File

@ -0,0 +1,284 @@
// 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-utils/contracts/src/v06/LibBytesV06.sol";
import "../fixins/FixinCommon.sol";
import "../fixins/FixinEIP712.sol";
import "./interfaces/IFeature.sol";
import "./interfaces/IDCAFeature.sol";
import "./libs/LibSignature.sol";
import "../migrations/LibMigrate.sol";
import "./interfaces/ITransformERC20Feature.sol";
import "forge-std/Test.sol";
/// @dev DCA feature.
contract DCAFeature is IFeature, IDCAFeature, FixinCommon, FixinEIP712 {
using LibBytesV06 for bytes;
using LibRichErrorsV06 for bytes;
/// @dev Describes the state of the DCA.
struct ExecuteState {
// Hash of the DCA order.
bytes32 hash;
// The DCA order data;
DCAData dca;
// The DCA signature (by `dca.signer`).
LibSignature.Signature signature;
// The calldata to fill for the current iteration of the DCA.
bytes swapCalldata;
}
/// @dev Arguments for a `TransformERC20.transformERC20()` call.
struct ExternalTransformERC20Args {
IERC20Token inputToken;
IERC20Token outputToken;
uint256 inputTokenAmount;
uint256 minOutputTokenAmount;
ITransformERC20Feature.Transformation[] transformations;
}
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "DCA";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
/// @dev EIP712 typehash of the `DCAData` struct.
bytes32 public constant DCA_DATA_TYPEHASH =
keccak256(
"DCAData("
"address buyToken,"
"address sellToken,"
"uint256 sellAmount,"
"uint256 interval,"
"uint256 numFills,"
"uint256 startTime,"
"address signer"
")"
);
/// @dev number of fills executed per DCA order.
mapping(bytes32 => uint256) public numFilled;
constructor(address zeroExAddress) public FixinCommon() FixinEIP712(zeroExAddress) {}
function migrate() external returns (bytes4 success) {
_registerFeatureFunction(this.fillDCATransaction.selector);
_registerFeatureFunction(this.getDCAHash.selector);
return LibMigrate.MIGRATE_SUCCESS;
}
function fillDCATransaction(
DCAData calldata dcaData,
LibSignature.Signature calldata signature,
bytes calldata swapCallData
) public override returns (bytes memory returnResult) {
ExecuteState memory state;
state.dca = dcaData;
state.hash = getDCAHash(dcaData);
state.signature = signature;
state.swapCalldata = swapCallData;
returnResult = _fillDCATransaction(state);
}
/// @dev Get the EIP712 hash of a dca order.
/// @param dca The DCA order.
/// @return dcaHash the EIP712 hash of 'dca'.
function getDCAHash(DCAData memory dca) public view override returns (bytes32 dcaHash) {
return
_getEIP712Hash(
keccak256(
abi.encode(
DCA_DATA_TYPEHASH,
dca.buyToken,
dca.sellToken,
dca.sellAmount,
dca.interval,
dca.numFills,
dca.startTime,
dca.signer
)
)
);
}
function _fillDCATransaction(ExecuteState memory state) private returns (bytes memory returnResult) {
_validateDCATransaction(state);
// get balances of sellToken and buyToken before we execute calldata
uint256 sellTokenBalanceBefore = state.dca.sellToken.balanceOf(state.dca.signer);
uint256 buyTokenBalanceBefore = state.dca.buyToken.balanceOf(state.dca.signer);
// execute the calldata
bytes4 selector = state.swapCalldata.readBytes4(0);
if (selector == ITransformERC20Feature.transformERC20.selector) {
returnResult = _executeTransformERC20Call(state);
} else {
revert("Unsupported swap function");
}
// get the balances of the sellToken and buyToken after we execute the calldata
uint256 sellTokenBalanceAfter = state.dca.sellToken.balanceOf(state.dca.signer);
uint256 buyTokenBalanceAfter = state.dca.buyToken.balanceOf(state.dca.signer);
// validate deltas
if (sellTokenBalanceAfter > sellTokenBalanceBefore) {
revert("Sell token balance increased");
}
if (sellTokenBalanceBefore - sellTokenBalanceAfter != state.dca.sellAmount) {
revert("Sell token balance delta does not match sell amount");
}
if (buyTokenBalanceAfter < buyTokenBalanceBefore) {
revert("Buy token balance decreased");
}
// TODO query oracle to ensure minslippage is achieved
// update storage to show that we have executed the order
numFilled[state.hash] += 1;
}
function _validateDCATransaction(ExecuteState memory state) private view {
// validate that the signature
if (LibSignature.getSignerOfHash(state.hash, state.signature) != state.dca.signer) {
LibSignatureRichErrors
.SignatureValidationError(
LibSignatureRichErrors.SignatureValidationErrorCodes.WRONG_SIGNER,
state.hash,
state.dca.signer,
// TODO: Remove this field from SignatureValidationError
// when rich reverts are part of the protocol repo.
""
)
.rrevert();
}
// check if all DCA orders have been executed already
if (numFilled[state.hash] == state.dca.numFills) {
revert("All DCA orders have been executed already");
}
// Check that DCA order is within the right time window
uint256 endTime = state.dca.startTime + (state.dca.interval * state.dca.numFills);
if (block.timestamp < state.dca.startTime || block.timestamp > endTime) {
revert("Invalid time window");
}
uint256 currTime = block.timestamp;
uint256 currFill = (state.dca.numFills * (currTime - state.dca.startTime)) / (endTime - state.dca.startTime); // zero-indexed
uint256 currWindowMidpoint = ((currFill + 1) * (endTime - state.dca.startTime)) / state.dca.numFills;
currWindowMidpoint = currWindowMidpoint + state.dca.startTime;
uint256 halfWindowSizeSeconds = 150; // 2.5 minutes
uint256 currWindowStart = currWindowMidpoint < halfWindowSizeSeconds
? 0
: currWindowMidpoint - halfWindowSizeSeconds;
uint256 currWindowEnd = currWindowMidpoint + halfWindowSizeSeconds;
if (currTime < currWindowStart || currTime > currWindowEnd) {
console.log(
"currTime=%d, currWindowMidpoint=%d, halfWindow=%d",
currTime,
currWindowMidpoint,
halfWindowSizeSeconds
);
revert("Invalid time window 2");
}
}
/// @dev Execute a `ITransformERC20Feature.transformERC20()` meta-transaction call
/// by decoding the call args and translating the call to the internal
/// `ITransformERC20Feature._transformERC20()` variant, where we can override
/// the taker address.
function _executeTransformERC20Call(ExecuteState memory state) private returns (bytes memory returnResult) {
// HACK(dorothy-zbornak): `abi.decode()` with the individual args
// will cause a stack overflow. But we can prefix the call data with an
// offset to transform it into the encoding for the equivalent single struct arg,
// since decoding a single struct arg consumes far less stack space than
// decoding multiple struct args.
// Where the encoding for multiple args (with the selector ommitted)
// would typically look like:
// | argument | offset |
// |--------------------------|---------|
// | inputToken | 0 |
// | outputToken | 32 |
// | inputTokenAmount | 64 |
// | minOutputTokenAmount | 96 |
// | transformations (offset) | 128 | = 32
// | transformations (data) | 160 |
// We will ABI-decode a single struct arg copy with the layout:
// | argument | offset |
// |--------------------------|---------|
// | (arg 1 offset) | 0 | = 32
// | inputToken | 32 |
// | outputToken | 64 |
// | inputTokenAmount | 96 |
// | minOutputTokenAmount | 128 |
// | transformations (offset) | 160 | = 32
// | transformations (data) | 192 |
ExternalTransformERC20Args memory args;
{
bytes memory encodedStructArgs = new bytes(state.swapCalldata.length - 4 + 32);
// Copy the args data from the original, after the new struct offset prefix.
bytes memory fromCallData = state.swapCalldata;
assert(fromCallData.length >= 160);
uint256 fromMem;
uint256 toMem;
assembly {
// Prefix the calldata with a struct offset,
// which points to just one word over.
mstore(add(encodedStructArgs, 32), 32)
// Copy everything after the selector.
fromMem := add(fromCallData, 36)
// Start copying after the struct offset.
toMem := add(encodedStructArgs, 64)
}
LibBytesV06.memCopy(toMem, fromMem, fromCallData.length - 4);
// Decode call args for `ITransformERC20Feature.transformERC20()` as a struct.
args = abi.decode(encodedStructArgs, (ExternalTransformERC20Args));
}
// Call `ITransformERC20Feature._transformERC20()` (internal variant).
return
_callSelf(
abi.encodeWithSelector(
ITransformERC20Feature._transformERC20.selector,
ITransformERC20Feature.TransformERC20Args({
taker: state.dca.signer, // taker is mtx signer
inputToken: args.inputToken,
outputToken: args.outputToken,
inputTokenAmount: args.inputTokenAmount,
minOutputTokenAmount: args.minOutputTokenAmount,
transformations: args.transformations,
useSelfBalance: false,
recipient: state.dca.signer
})
)
);
}
/// @dev Make an arbitrary internal, meta-transaction call.
/// Warning: Do not let unadulterated `callData` into this function.
function _callSelf(bytes memory callData) private returns (bytes memory returnResult) {
bool success;
(success, returnResult) = address(this).call(callData);
if (!success) {
console.logBytes(returnResult);
revert("Swap execution failed ");
}
}
}

View File

@ -0,0 +1,47 @@
// 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/src/IERC20Token.sol";
import "../libs/LibSignature.sol";
/// @dev DCA feature.
interface IDCAFeature {
struct DCAData {
// The token to buy.
IERC20Token buyToken;
// The token to sell.
IERC20Token sellToken;
// The amount of sellToken to sell.
uint256 sellAmount;
// The amount of time between each fill in seconds.
uint256 interval;
// The number of fills to execute.
uint256 numFills;
// The start time of the DCA order in Unix epoch seconds.
uint256 startTime;
// Signer of the DCA. On whose behalf to execute the DCA.
address payable signer;
}
function fillDCATransaction(
DCAData calldata dcaData,
LibSignature.Signature calldata signature,
bytes calldata swapCallData
) external returns (bytes memory returnResult);
function getDCAHash(DCAData calldata dcaData) external view returns (bytes32 dcaHash);
}

View File

@ -0,0 +1,115 @@
// 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 "forge-std/Test.sol";
import "forge-std/StdUtils.sol";
import "./utils/LocalTest.sol";
import "../contracts/src/features/DCAFeature.sol";
import "../contracts/src/features/interfaces/IDCAFeature.sol";
import "@0x/contracts-erc20/src/IERC20Token.sol";
import "../contracts/src/fixins/FixinEIP712.sol";
import "../contracts/src/features/libs/LibSignature.sol";
contract DCAFeatureTest is LocalTest {
function test_validateSignature() public {
uint256 signerPrivateKey = 0xA11CE;
address signer = vm.addr(signerPrivateKey);
IDCAFeature.DCAData memory dcaData = IDCAFeature.DCAData({
buyToken: IERC20Token(address(0)),
sellToken: IERC20Token(address(0)),
sellAmount: 100,
interval: 100,
numFills: 8,
startTime: block.timestamp,
signer: payable(signer)
});
bytes32 dcaHash = zeroExDeployed.features.dcaFeature.getDCAHash(dcaData);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, dcaHash);
LibSignature.Signature memory signature = LibSignature.Signature({
signatureType: LibSignature.SignatureType.EIP712,
v: v,
r: r,
s: s
});
assertEq(LibSignature.getSignerOfHash(dcaHash, signature), dcaData.signer);
}
function test_checkNumFilled() public {
// check that if user signed order to execute order two times, we expect
// a revert on the third try
}
function test_timeWindow() public {
// spoof the blockchain timestmap and expect a revert if we're outside
}
function test_dcaSwap() public {
uint256 signerPrivateKey = 0xA11CE;
address signer = vm.addr(signerPrivateKey);
IDCAFeature.DCAData memory dcaData = IDCAFeature.DCAData({
// buyToken: IERC20Token(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2),
// sellToken: IERC20Token(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48),
buyToken: zrx,
sellToken: dai,
sellAmount: 100,
interval: 600,
numFills: 12,
startTime: block.timestamp,
signer: payable(signer)
});
vm.warp(600); // warp the blockchain 10 minutes into the future
bytes32 dcaHash = zeroExDeployed.features.dcaFeature.getDCAHash(dcaData);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, dcaHash);
LibSignature.Signature memory signature = LibSignature.Signature({
signatureType: LibSignature.SignatureType.EIP712,
v: v,
r: r,
s: s
});
ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](1);
transformations[0] = ITransformERC20Feature.Transformation(
uint32(transformerNonce),
abi.encode(address(dcaData.sellToken), address(dcaData.buyToken), 0, 1e18, 0)
);
console.log(transformerNonce);
_mintTo(address(dcaData.sellToken), signer, 1e18);
vm.prank(signer);
dcaData.sellToken.approve(address(zeroExDeployed.zeroEx), 1e18);
bytes memory swapCalldata = abi.encodeWithSelector(
zeroExDeployed.zeroEx.transformERC20.selector,
dcaData.sellToken,
dcaData.buyToken,
1e18,
1e18,
transformations
);
assertEq(dai.balanceOf(signer), 1e18);
IDCAFeature(address(zeroExDeployed.zeroEx)).fillDCATransaction(dcaData, signature, swapCalldata);
}
}

View File

@ -29,6 +29,7 @@ import "src/features/TransformERC20Feature.sol";
import "src/features/OtcOrdersFeature.sol"; import "src/features/OtcOrdersFeature.sol";
import "src/features/MetaTransactionsFeature.sol"; import "src/features/MetaTransactionsFeature.sol";
import "src/features/MetaTransactionsFeatureV2.sol"; import "src/features/MetaTransactionsFeatureV2.sol";
import "src/features/DCAFeature.sol";
import "src/features/nft_orders/ERC1155OrdersFeature.sol"; import "src/features/nft_orders/ERC1155OrdersFeature.sol";
import "src/features/nft_orders/ERC721OrdersFeature.sol"; import "src/features/nft_orders/ERC721OrdersFeature.sol";
import "src/features/UniswapFeature.sol"; import "src/features/UniswapFeature.sol";
@ -70,6 +71,7 @@ contract DeployZeroEx is Test {
TransformERC20Feature transformERC20Feature; TransformERC20Feature transformERC20Feature;
MetaTransactionsFeature metaTransactionsFeature; MetaTransactionsFeature metaTransactionsFeature;
MetaTransactionsFeatureV2 metaTransactionsFeatureV2; MetaTransactionsFeatureV2 metaTransactionsFeatureV2;
DCAFeature dcaFeature;
ERC1155OrdersFeature erc1155OrdersFeature; ERC1155OrdersFeature erc1155OrdersFeature;
ERC721OrdersFeature erc721OrdersFeature; ERC721OrdersFeature erc721OrdersFeature;
MultiplexFeature multiplexFeature; MultiplexFeature multiplexFeature;
@ -139,6 +141,7 @@ contract DeployZeroEx is Test {
"MetaTransactionsFeatureV2", "MetaTransactionsFeatureV2",
address(ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2) address(ZERO_EX_DEPLOYED.features.metaTransactionsFeatureV2)
); );
emit log_named_address("DCAFeature", address(ZERO_EX_DEPLOYED.features.dcaFeature));
emit log_named_address("ERC1155OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature)); emit log_named_address("ERC1155OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature));
emit log_named_address("ERC721OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc721OrdersFeature)); emit log_named_address("ERC721OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc721OrdersFeature));
emit log_named_address("TransformERC20Feature", address(ZERO_EX_DEPLOYED.features.transformERC20Feature)); emit log_named_address("TransformERC20Feature", address(ZERO_EX_DEPLOYED.features.transformERC20Feature));
@ -229,6 +232,8 @@ contract DeployZeroEx is Test {
ZERO_EX_DEPLOY_CONFIG.sushiswapPairInitCodeHash ZERO_EX_DEPLOY_CONFIG.sushiswapPairInitCodeHash
); );
ZERO_EX_DEPLOYED.features.dcaFeature = new DCAFeature(address(ZERO_EX));
initialMigration.initializeZeroEx( initialMigration.initializeZeroEx(
payable(address(this)), payable(address(this)),
ZERO_EX, ZERO_EX,
@ -285,6 +290,11 @@ contract DeployZeroEx is Test {
abi.encodeWithSelector(MetaTransactionsFeatureV2.migrate.selector), abi.encodeWithSelector(MetaTransactionsFeatureV2.migrate.selector),
address(this) address(this)
); );
IZERO_EX.migrate(
address(ZERO_EX_DEPLOYED.features.dcaFeature),
abi.encodeWithSelector(DCAFeature.migrate.selector),
address(this)
);
IZERO_EX.migrate( IZERO_EX.migrate(
address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature), address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature),
abi.encodeWithSelector(ERC1155OrdersFeature.migrate.selector), abi.encodeWithSelector(ERC1155OrdersFeature.migrate.selector),