DCA Orders
This commit is contained in:
36
contracts/zero-ex/contracts/scripts/ApproveMe.sol
Normal file
36
contracts/zero-ex/contracts/scripts/ApproveMe.sol
Normal 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();
|
||||
}
|
||||
}
|
38
contracts/zero-ex/contracts/scripts/DealMe.sol
Normal file
38
contracts/zero-ex/contracts/scripts/DealMe.sol
Normal 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();
|
||||
}
|
||||
}
|
83
contracts/zero-ex/contracts/scripts/Debugger.sol
Normal file
83
contracts/zero-ex/contracts/scripts/Debugger.sol
Normal 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();
|
||||
}
|
||||
}
|
39
contracts/zero-ex/contracts/scripts/Deploy.sol
Normal file
39
contracts/zero-ex/contracts/scripts/Deploy.sol
Normal 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();
|
||||
}
|
||||
}
|
68
contracts/zero-ex/contracts/scripts/TestDeploy.sol
Normal file
68
contracts/zero-ex/contracts/scripts/TestDeploy.sol
Normal 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);
|
||||
}
|
||||
}
|
284
contracts/zero-ex/contracts/src/features/DCAFeature.sol
Normal file
284
contracts/zero-ex/contracts/src/features/DCAFeature.sol
Normal 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 ");
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
115
contracts/zero-ex/tests/DCATest.t.sol
Normal file
115
contracts/zero-ex/tests/DCATest.t.sol
Normal 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);
|
||||
}
|
||||
}
|
@@ -29,6 +29,7 @@ import "src/features/TransformERC20Feature.sol";
|
||||
import "src/features/OtcOrdersFeature.sol";
|
||||
import "src/features/MetaTransactionsFeature.sol";
|
||||
import "src/features/MetaTransactionsFeatureV2.sol";
|
||||
import "src/features/DCAFeature.sol";
|
||||
import "src/features/nft_orders/ERC1155OrdersFeature.sol";
|
||||
import "src/features/nft_orders/ERC721OrdersFeature.sol";
|
||||
import "src/features/UniswapFeature.sol";
|
||||
@@ -70,6 +71,7 @@ contract DeployZeroEx is Test {
|
||||
TransformERC20Feature transformERC20Feature;
|
||||
MetaTransactionsFeature metaTransactionsFeature;
|
||||
MetaTransactionsFeatureV2 metaTransactionsFeatureV2;
|
||||
DCAFeature dcaFeature;
|
||||
ERC1155OrdersFeature erc1155OrdersFeature;
|
||||
ERC721OrdersFeature erc721OrdersFeature;
|
||||
MultiplexFeature multiplexFeature;
|
||||
@@ -139,6 +141,7 @@ contract DeployZeroEx is Test {
|
||||
"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("ERC721OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc721OrdersFeature));
|
||||
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_DEPLOYED.features.dcaFeature = new DCAFeature(address(ZERO_EX));
|
||||
|
||||
initialMigration.initializeZeroEx(
|
||||
payable(address(this)),
|
||||
ZERO_EX,
|
||||
@@ -285,6 +290,11 @@ contract DeployZeroEx is Test {
|
||||
abi.encodeWithSelector(MetaTransactionsFeatureV2.migrate.selector),
|
||||
address(this)
|
||||
);
|
||||
IZERO_EX.migrate(
|
||||
address(ZERO_EX_DEPLOYED.features.dcaFeature),
|
||||
abi.encodeWithSelector(DCAFeature.migrate.selector),
|
||||
address(this)
|
||||
);
|
||||
IZERO_EX.migrate(
|
||||
address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature),
|
||||
abi.encodeWithSelector(ERC1155OrdersFeature.migrate.selector),
|
||||
|
Reference in New Issue
Block a user