Compare commits
1 Commits
developmen
...
exp/DCAOrd
Author | SHA1 | Date | |
---|---|---|---|
|
2851637ee4 |
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/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),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user