Merge pull request #2084 from 0xProject/feat/3.0/transactionGasPrice
Add `gasPrice` to 0x transactions
This commit is contained in:
@@ -91,7 +91,7 @@ jobs:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-extensions @0x/contracts-asset-proxy @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-coordinator @0x/contracts-dev-utils @0x/contracts-staking
|
||||
test-contracts-ganache-3.0:
|
||||
test-exchange-ganache-3.0:
|
||||
resource_class: medium+
|
||||
docker:
|
||||
- image: nikolaik/python-nodejs:python3.7-nodejs8
|
||||
@@ -100,7 +100,17 @@ jobs:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-dev-utils @0x/contracts-staking
|
||||
- run: yarn wsrun test:circleci @0x/contracts-exchange
|
||||
test-contracts-rest-ganache-3.0:
|
||||
resource_class: medium+
|
||||
docker:
|
||||
- image: nikolaik/python-nodejs:python3.7-nodejs8
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange-forwarder @0x/contracts-dev-utils @0x/contracts-staking
|
||||
# TODO(dorothy-zbornak): Re-enable after updating this package for 3.0.
|
||||
# - run: yarn wsrun test:circleci @0x/contracts-extensions
|
||||
# TODO(abandeali): Re-enable after this package is complete.
|
||||
@@ -563,7 +573,10 @@ workflows:
|
||||
# - build-website:
|
||||
# requires:
|
||||
# - build
|
||||
- test-contracts-ganache-3.0:
|
||||
- test-exchange-ganache-3.0:
|
||||
requires:
|
||||
- build-3.0
|
||||
- test-contracts-rest-ganache-3.0:
|
||||
requires:
|
||||
- build-3.0
|
||||
# Disabled until geth docker image is fixed.
|
||||
@@ -590,7 +603,8 @@ workflows:
|
||||
# - build-3.0
|
||||
- submit-coverage-3.0:
|
||||
requires:
|
||||
- test-contracts-ganache-3.0
|
||||
- test-contracts-rest-ganache-3.0
|
||||
- test-exchange-ganache-3.0
|
||||
- test-rest-3.0
|
||||
- static-tests-3.0
|
||||
# Disabled for 3.0
|
||||
|
@@ -20,7 +20,7 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('EtherToken', () => {
|
||||
let account: string;
|
||||
const gasPrice = Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 9);
|
||||
const gasPrice = new BigNumber(constants.DEFAULT_GAS_PRICE);
|
||||
let etherToken: WETH9Contract;
|
||||
|
||||
before(async () => {
|
||||
|
@@ -20,7 +20,7 @@ import { artifacts, ForwarderContract, ForwarderTestFactory, ForwarderWrapper }
|
||||
|
||||
const DECIMALS_DEFAULT = 18;
|
||||
|
||||
blockchainTests.only(ContractName.Forwarder, env => {
|
||||
blockchainTests(ContractName.Forwarder, env => {
|
||||
let chainId: number;
|
||||
let makerAddress: string;
|
||||
let owner: string;
|
||||
@@ -45,7 +45,7 @@ blockchainTests.only(ContractName.Forwarder, env => {
|
||||
let tx: TransactionReceiptWithDecodedLogs;
|
||||
|
||||
let erc721MakerAssetIds: BigNumber[];
|
||||
let gasPrice: BigNumber;
|
||||
const gasPrice = new BigNumber(constants.DEFAULT_GAS_PRICE);
|
||||
|
||||
before(async () => {
|
||||
await env.blockchainLifecycle.startAsync();
|
||||
@@ -61,10 +61,6 @@ blockchainTests.only(ContractName.Forwarder, env => {
|
||||
forwarderFeeRecipientAddress,
|
||||
] = accounts);
|
||||
|
||||
const txHash = await env.web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 });
|
||||
const transaction = await env.web3Wrapper.getTransactionByHashAsync(txHash);
|
||||
gasPrice = new BigNumber(transaction.gasPrice);
|
||||
|
||||
const erc721Wrapper = new ERC721Wrapper(env.provider, usedAddresses, owner);
|
||||
erc20Wrapper = new ERC20Wrapper(env.provider, usedAddresses, owner);
|
||||
|
||||
|
@@ -53,7 +53,6 @@ library LibExchangeRichErrors {
|
||||
}
|
||||
|
||||
enum TransactionErrorCodes {
|
||||
NO_REENTRANCY,
|
||||
ALREADY_EXECUTED,
|
||||
EXPIRED
|
||||
}
|
||||
@@ -131,6 +130,14 @@ library LibExchangeRichErrors {
|
||||
// bytes4(keccak256("TransactionExecutionError(bytes32,bytes)"))
|
||||
bytes4 internal constant TRANSACTION_EXECUTION_ERROR_SELECTOR =
|
||||
0x20d11f61;
|
||||
|
||||
// bytes4(keccak256("TransactionGasPriceError(bytes32,uint256,uint256)"))
|
||||
bytes4 internal constant TRANSACTION_GAS_PRICE_ERROR_SELECTOR =
|
||||
0xa26dac09;
|
||||
|
||||
// bytes4(keccak256("TransactionInvalidContextError(bytes32,address)"))
|
||||
bytes4 internal constant TRANSACTION_INVALID_CONTEXT_ERROR_SELECTOR =
|
||||
0xdec4aedf;
|
||||
|
||||
// bytes4(keccak256("IncompleteFillError(uint8,uint256,uint256)"))
|
||||
bytes4 internal constant INCOMPLETE_FILL_ERROR_SELECTOR =
|
||||
@@ -293,6 +300,14 @@ library LibExchangeRichErrors {
|
||||
return BATCH_MATCH_ORDERS_ERROR_SELECTOR;
|
||||
}
|
||||
|
||||
function TransactionGasPriceErrorSelector()
|
||||
internal
|
||||
pure
|
||||
returns (bytes4)
|
||||
{
|
||||
return TRANSACTION_GAS_PRICE_ERROR_SELECTOR;
|
||||
}
|
||||
|
||||
function BatchMatchOrdersError(
|
||||
BatchMatchOrdersErrorCodes errorCode
|
||||
)
|
||||
@@ -581,6 +596,38 @@ library LibExchangeRichErrors {
|
||||
);
|
||||
}
|
||||
|
||||
function TransactionGasPriceError(
|
||||
bytes32 transactionHash,
|
||||
uint256 actualGasPrice,
|
||||
uint256 requiredGasPrice
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
TRANSACTION_GAS_PRICE_ERROR_SELECTOR,
|
||||
transactionHash,
|
||||
actualGasPrice,
|
||||
requiredGasPrice
|
||||
);
|
||||
}
|
||||
|
||||
function TransactionInvalidContextError(
|
||||
bytes32 transactionHash,
|
||||
address currentContextAddress
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
TRANSACTION_INVALID_CONTEXT_ERROR_SELECTOR,
|
||||
transactionHash,
|
||||
currentContextAddress
|
||||
);
|
||||
}
|
||||
|
||||
function IncompleteFillError(
|
||||
IncompleteFillErrorCode errorCode,
|
||||
uint256 expectedAssetFillAmount,
|
||||
|
@@ -30,16 +30,18 @@ library LibZeroExTransaction {
|
||||
// keccak256(abi.encodePacked(
|
||||
// "ZeroExTransaction(",
|
||||
// "uint256 salt,",
|
||||
// "uint256 expirationTimeSeconds,"
|
||||
// "uint256 expirationTimeSeconds,",
|
||||
// "uint256 gasPrice,",
|
||||
// "address signerAddress,",
|
||||
// "bytes data",
|
||||
// ")"
|
||||
// ));
|
||||
bytes32 constant internal _EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = 0x6b4c70d217b44d0ff0d3bf7aeb18eb8604c5cd06f615a4b497aeefa4f01d2775;
|
||||
bytes32 constant internal _EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = 0xec69816980a3a3ca4554410e60253953e9ff375ba4536a98adfa15cc71541508;
|
||||
|
||||
struct ZeroExTransaction {
|
||||
uint256 salt; // Arbitrary number to ensure uniqueness of transaction hash.
|
||||
uint256 expirationTimeSeconds; // Timestamp in seconds at which transaction expires.
|
||||
uint256 gasPrice; // gasPrice at which transaction is required to be executed with.
|
||||
address signerAddress; // Address of transaction signer.
|
||||
bytes data; // AbiV2 encoded calldata.
|
||||
}
|
||||
@@ -72,15 +74,17 @@ library LibZeroExTransaction {
|
||||
bytes memory data = transaction.data;
|
||||
uint256 salt = transaction.salt;
|
||||
uint256 expirationTimeSeconds = transaction.expirationTimeSeconds;
|
||||
uint256 gasPrice = transaction.gasPrice;
|
||||
address signerAddress = transaction.signerAddress;
|
||||
|
||||
// Assembly for more efficiently computing:
|
||||
// keccak256(abi.encodePacked(
|
||||
// EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH,
|
||||
// transaction.salt,
|
||||
// transaction.expirationTimeSeconds,
|
||||
// uint256(transaction.signerAddress),
|
||||
// keccak256(transaction.data)
|
||||
// result = keccak256(abi.encodePacked(
|
||||
// schemaHash,
|
||||
// salt,
|
||||
// expirationTimeSeconds,
|
||||
// gasPrice,
|
||||
// uint256(signerAddress),
|
||||
// keccak256(data)
|
||||
// ));
|
||||
|
||||
assembly {
|
||||
@@ -90,14 +94,15 @@ library LibZeroExTransaction {
|
||||
// Load free memory pointer
|
||||
let memPtr := mload(64)
|
||||
|
||||
mstore(memPtr, schemaHash) // hash of schema
|
||||
mstore(add(memPtr, 32), salt) // salt
|
||||
mstore(add(memPtr, 64), expirationTimeSeconds) // expirationTimeSeconds
|
||||
mstore(add(memPtr, 96), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress
|
||||
mstore(add(memPtr, 128), dataHash) // hash of data
|
||||
mstore(memPtr, schemaHash) // hash of schema
|
||||
mstore(add(memPtr, 32), salt) // salt
|
||||
mstore(add(memPtr, 64), expirationTimeSeconds) // expirationTimeSeconds
|
||||
mstore(add(memPtr, 96), gasPrice) // gasPrice
|
||||
mstore(add(memPtr, 128), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress
|
||||
mstore(add(memPtr, 160), dataHash) // hash of data
|
||||
|
||||
// Compute hash
|
||||
result := keccak256(memPtr, 160)
|
||||
result := keccak256(memPtr, 192)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ blockchainTests('LibZeroExTransaction', env => {
|
||||
const EMPTY_TRANSACTION: ZeroExTransaction = {
|
||||
salt: constants.ZERO_AMOUNT,
|
||||
expirationTimeSeconds: constants.ZERO_AMOUNT,
|
||||
gasPrice: constants.ZERO_AMOUNT,
|
||||
signerAddress: constants.NULL_ADDRESS,
|
||||
data: constants.NULL_BYTES,
|
||||
domain: {
|
||||
@@ -66,6 +67,7 @@ blockchainTests('LibZeroExTransaction', env => {
|
||||
await testGetTypedDataHashAsync({
|
||||
salt: randomUint256(),
|
||||
expirationTimeSeconds: randomUint256(),
|
||||
gasPrice: randomUint256(),
|
||||
signerAddress: randomAddress(),
|
||||
data: randomAssetData(),
|
||||
domain: {
|
||||
@@ -121,6 +123,7 @@ blockchainTests('LibZeroExTransaction', env => {
|
||||
await testGetStructHashAsync({
|
||||
salt: randomUint256(),
|
||||
expirationTimeSeconds: randomUint256(),
|
||||
gasPrice: randomUint256(),
|
||||
signerAddress: randomAddress(),
|
||||
data: randomAssetData(),
|
||||
// The domain is not used in this test, so it's okay if it is left empty.
|
||||
|
@@ -61,6 +61,7 @@ contract ExchangeWrapper {
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction = LibZeroExTransaction.ZeroExTransaction({
|
||||
salt: salt,
|
||||
expirationTimeSeconds: transactionExpirationTimeSeconds,
|
||||
gasPrice: tx.gasprice,
|
||||
data: data,
|
||||
signerAddress: makerAddress
|
||||
});
|
||||
@@ -99,6 +100,7 @@ contract ExchangeWrapper {
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction = LibZeroExTransaction.ZeroExTransaction({
|
||||
salt: salt,
|
||||
expirationTimeSeconds: transactionExpirationTimeSeconds,
|
||||
gasPrice: tx.gasprice,
|
||||
data: data,
|
||||
signerAddress: takerAddress
|
||||
});
|
||||
|
@@ -134,6 +134,7 @@ contract Whitelist is
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction = LibZeroExTransaction.ZeroExTransaction({
|
||||
salt: salt,
|
||||
data: data,
|
||||
gasPrice: tx.gasprice,
|
||||
expirationTimeSeconds: uint256(-1),
|
||||
signerAddress: takerAddress
|
||||
});
|
||||
|
@@ -361,11 +361,14 @@ contract MixinExchangeCore is
|
||||
orderInfo.orderHash,
|
||||
makerAddress,
|
||||
signature
|
||||
)) {
|
||||
)
|
||||
) {
|
||||
if (!_isValidOrderWithHashSignature(
|
||||
order,
|
||||
orderInfo.orderHash,
|
||||
signature)) {
|
||||
signature
|
||||
)
|
||||
) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError(
|
||||
LibExchangeRichErrors.SignatureErrorCodes.BAD_SIGNATURE,
|
||||
orderInfo.orderHash,
|
||||
|
@@ -42,7 +42,7 @@ contract MixinTransactions is
|
||||
address public currentContextAddress;
|
||||
|
||||
/// @dev Executes an Exchange method call in the context of signer.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @param transaction 0x transaction structure.
|
||||
/// @param signature Proof that transaction has been signed by signer.
|
||||
/// @return ABI encoded return data of the underlying Exchange function call.
|
||||
function executeTransaction(
|
||||
@@ -56,7 +56,7 @@ contract MixinTransactions is
|
||||
}
|
||||
|
||||
/// @dev Executes a batch of Exchange method calls in the context of signer(s).
|
||||
/// @param transactions Array of 0x transactions containing salt, signerAddress, and data.
|
||||
/// @param transactions Array of 0x transaction structures.
|
||||
/// @param signatures Array of proofs that transactions have been signed by signer(s).
|
||||
/// @return Array containing ABI encoded return data for each of the underlying Exchange function calls.
|
||||
function batchExecuteTransactions(
|
||||
@@ -75,7 +75,7 @@ contract MixinTransactions is
|
||||
}
|
||||
|
||||
/// @dev Executes an Exchange method call in the context of signer.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @param transaction 0x transaction structure.
|
||||
/// @param signature Proof that transaction has been signed by signer.
|
||||
/// @return ABI encoded return data of the underlying Exchange function call.
|
||||
function _executeTransaction(
|
||||
@@ -87,46 +87,14 @@ contract MixinTransactions is
|
||||
{
|
||||
bytes32 transactionHash = transaction.getTypedDataHash(EIP712_EXCHANGE_DOMAIN_HASH);
|
||||
|
||||
// Check transaction is not expired
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
if (block.timestamp >= transaction.expirationTimeSeconds) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.TransactionError(
|
||||
LibExchangeRichErrors.TransactionErrorCodes.EXPIRED,
|
||||
transactionHash
|
||||
));
|
||||
}
|
||||
_assertExecutableTransaction(
|
||||
transaction,
|
||||
signature,
|
||||
transactionHash
|
||||
);
|
||||
|
||||
// Prevent reentrancy
|
||||
if (currentContextAddress != address(0)) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.TransactionError(
|
||||
LibExchangeRichErrors.TransactionErrorCodes.NO_REENTRANCY,
|
||||
transactionHash
|
||||
));
|
||||
}
|
||||
|
||||
// Validate transaction has not been executed
|
||||
if (transactionsExecuted[transactionHash]) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.TransactionError(
|
||||
LibExchangeRichErrors.TransactionErrorCodes.ALREADY_EXECUTED,
|
||||
transactionHash
|
||||
));
|
||||
}
|
||||
|
||||
// Transaction always valid if signer is sender of transaction
|
||||
address signerAddress = transaction.signerAddress;
|
||||
if (signerAddress != msg.sender) {
|
||||
// Validate signature
|
||||
if (!_isValidTransactionWithHashSignature(
|
||||
transaction,
|
||||
transactionHash,
|
||||
signature)) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.TransactionSignatureError(
|
||||
transactionHash,
|
||||
signerAddress,
|
||||
signature
|
||||
));
|
||||
}
|
||||
|
||||
// Set the current transaction signer
|
||||
currentContextAddress = signerAddress;
|
||||
}
|
||||
@@ -151,6 +119,71 @@ contract MixinTransactions is
|
||||
return returnData;
|
||||
}
|
||||
|
||||
/// @dev Validates context for executeTransaction. Succeeds or throws.
|
||||
/// @param transaction 0x transaction structure.
|
||||
/// @param signature Proof that transaction has been signed by signer.
|
||||
/// @param transactionHash EIP712 typed data hash of 0x transaction.
|
||||
function _assertExecutableTransaction(
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction,
|
||||
bytes memory signature,
|
||||
bytes32 transactionHash
|
||||
)
|
||||
internal
|
||||
view
|
||||
{
|
||||
// Check transaction is not expired
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
if (block.timestamp >= transaction.expirationTimeSeconds) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.TransactionError(
|
||||
LibExchangeRichErrors.TransactionErrorCodes.EXPIRED,
|
||||
transactionHash
|
||||
));
|
||||
}
|
||||
|
||||
// Validate that transaction is executed with the correct gasPrice
|
||||
uint256 requiredGasPrice = transaction.gasPrice;
|
||||
if (tx.gasprice != requiredGasPrice) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.TransactionGasPriceError(
|
||||
transactionHash,
|
||||
tx.gasprice,
|
||||
requiredGasPrice
|
||||
));
|
||||
}
|
||||
|
||||
// Prevent `executeTransaction` from being called when context is already set
|
||||
address currentContextAddress_ = currentContextAddress;
|
||||
if (currentContextAddress_ != address(0)) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.TransactionInvalidContextError(
|
||||
transactionHash,
|
||||
currentContextAddress_
|
||||
));
|
||||
}
|
||||
|
||||
// Validate transaction has not been executed
|
||||
if (transactionsExecuted[transactionHash]) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.TransactionError(
|
||||
LibExchangeRichErrors.TransactionErrorCodes.ALREADY_EXECUTED,
|
||||
transactionHash
|
||||
));
|
||||
}
|
||||
|
||||
// Validate signature
|
||||
// Transaction always valid if signer is sender of transaction
|
||||
address signerAddress = transaction.signerAddress;
|
||||
if (signerAddress != msg.sender && !_isValidTransactionWithHashSignature(
|
||||
transaction,
|
||||
transactionHash,
|
||||
signature
|
||||
)
|
||||
) {
|
||||
LibRichErrors.rrevert(LibExchangeRichErrors.TransactionSignatureError(
|
||||
transactionHash,
|
||||
signerAddress,
|
||||
signature
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`).
|
||||
/// If calling a fill function, this address will represent the taker.
|
||||
/// If calling a cancel function, this address will represent the maker.
|
||||
|
@@ -57,4 +57,4 @@ contract MixinTransferSimulator is
|
||||
}
|
||||
revert("TRANSFERS_SUCCESSFUL");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
117
contracts/exchange/contracts/test/TestTransactions.sol
Normal file
117
contracts/exchange/contracts/test/TestTransactions.sol
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
|
||||
import "../src/Exchange.sol";
|
||||
|
||||
|
||||
contract TestTransactions is
|
||||
Exchange
|
||||
{
|
||||
event ExecutableCalled(
|
||||
bytes data,
|
||||
bytes returnData,
|
||||
address contextAddress
|
||||
);
|
||||
|
||||
constructor ()
|
||||
public
|
||||
Exchange(1337)
|
||||
{} // solhint-disable-line no-empty-blocks
|
||||
|
||||
function setCurrentContextAddress(address context)
|
||||
external
|
||||
{
|
||||
currentContextAddress = context;
|
||||
}
|
||||
|
||||
function setTransactionExecuted(bytes32 hash)
|
||||
external
|
||||
{
|
||||
transactionsExecuted[hash] = true;
|
||||
}
|
||||
|
||||
function getCurrentContextAddress()
|
||||
external
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
return _getCurrentContextAddress();
|
||||
}
|
||||
|
||||
function assertExecutableTransaction(
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction,
|
||||
bytes memory signature
|
||||
)
|
||||
public
|
||||
view
|
||||
{
|
||||
return _assertExecutableTransaction(
|
||||
transaction,
|
||||
signature,
|
||||
transaction.getTypedDataHash(EIP712_EXCHANGE_DOMAIN_HASH)
|
||||
);
|
||||
}
|
||||
|
||||
// This function will execute arbitrary calldata via a delegatecall. This is highly unsafe to use in production, and this
|
||||
// is only meant to be used during testing.
|
||||
function executable(
|
||||
bool shouldSucceed,
|
||||
bytes memory data,
|
||||
bytes memory returnData
|
||||
)
|
||||
public
|
||||
returns (bytes memory)
|
||||
{
|
||||
emit ExecutableCalled(
|
||||
data,
|
||||
returnData,
|
||||
currentContextAddress
|
||||
);
|
||||
require(shouldSucceed, "EXECUTABLE_FAILED");
|
||||
if (data.length != 0) {
|
||||
(bool didSucceed, bytes memory callResultData) = address(this).delegatecall(data); // This is a delegatecall to preserve the `msg.sender` field
|
||||
if (!didSucceed) {
|
||||
assembly { revert(add(callResultData, 0x20), mload(callResultData)) }
|
||||
}
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
|
||||
function _isValidTransactionWithHashSignature(
|
||||
LibZeroExTransaction.ZeroExTransaction memory,
|
||||
bytes32,
|
||||
bytes memory signature
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (bool)
|
||||
{
|
||||
if (
|
||||
signature.length == 2 &&
|
||||
signature[0] == 0x0 &&
|
||||
signature[1] == 0x0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -35,7 +35,7 @@
|
||||
"compile:truffle": "truffle compile"
|
||||
},
|
||||
"config": {
|
||||
"abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxy|IAssetProxyDispatcher|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|ISignatureValidator|ITransactions|ITransferSimulator|IWallet|IWrapperFunctions|IsolatedExchange|LibExchangeRichErrorDecoder|MixinAssetProxyDispatcher|MixinExchangeCore|MixinMatchOrders|MixinSignatureValidator|MixinTransactions|MixinTransferSimulator|MixinWrapperFunctions|ReentrancyTester|TestAssetProxyDispatcher|TestExchangeInternals|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestValidatorWallet|TestWrapperFunctions|Whitelist).json",
|
||||
"abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxy|IAssetProxyDispatcher|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|ISignatureValidator|ITransactions|ITransferSimulator|IWallet|IWrapperFunctions|IsolatedExchange|LibExchangeRichErrorDecoder|MixinAssetProxyDispatcher|MixinExchangeCore|MixinMatchOrders|MixinSignatureValidator|MixinTransactions|MixinTransferSimulator|MixinWrapperFunctions|ReentrancyTester|TestAssetProxyDispatcher|TestExchangeInternals|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestTransactions|TestValidatorWallet|TestWrapperFunctions|Whitelist).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
|
@@ -14,11 +14,11 @@ import * as IExchange from '../generated-artifacts/IExchange.json';
|
||||
import * as IExchangeCore from '../generated-artifacts/IExchangeCore.json';
|
||||
import * as IMatchOrders from '../generated-artifacts/IMatchOrders.json';
|
||||
import * as ISignatureValidator from '../generated-artifacts/ISignatureValidator.json';
|
||||
import * as IsolatedExchange from '../generated-artifacts/IsolatedExchange.json';
|
||||
import * as ITransactions from '../generated-artifacts/ITransactions.json';
|
||||
import * as ITransferSimulator from '../generated-artifacts/ITransferSimulator.json';
|
||||
import * as IWallet from '../generated-artifacts/IWallet.json';
|
||||
import * as IWrapperFunctions from '../generated-artifacts/IWrapperFunctions.json';
|
||||
import * as IsolatedExchange from '../generated-artifacts/IsolatedExchange.json';
|
||||
import * as LibExchangeRichErrorDecoder from '../generated-artifacts/LibExchangeRichErrorDecoder.json';
|
||||
import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json';
|
||||
import * as MixinExchangeCore from '../generated-artifacts/MixinExchangeCore.json';
|
||||
@@ -32,6 +32,7 @@ import * as TestAssetProxyDispatcher from '../generated-artifacts/TestAssetProxy
|
||||
import * as TestExchangeInternals from '../generated-artifacts/TestExchangeInternals.json';
|
||||
import * as TestLibExchangeRichErrorDecoder from '../generated-artifacts/TestLibExchangeRichErrorDecoder.json';
|
||||
import * as TestSignatureValidator from '../generated-artifacts/TestSignatureValidator.json';
|
||||
import * as TestTransactions from '../generated-artifacts/TestTransactions.json';
|
||||
import * as TestValidatorWallet from '../generated-artifacts/TestValidatorWallet.json';
|
||||
import * as TestWrapperFunctions from '../generated-artifacts/TestWrapperFunctions.json';
|
||||
import * as Whitelist from '../generated-artifacts/Whitelist.json';
|
||||
@@ -64,6 +65,7 @@ export const artifacts = {
|
||||
TestExchangeInternals: TestExchangeInternals as ContractArtifact,
|
||||
TestLibExchangeRichErrorDecoder: TestLibExchangeRichErrorDecoder as ContractArtifact,
|
||||
TestSignatureValidator: TestSignatureValidator as ContractArtifact,
|
||||
TestTransactions: TestTransactions as ContractArtifact,
|
||||
TestValidatorWallet: TestValidatorWallet as ContractArtifact,
|
||||
TestWrapperFunctions: TestWrapperFunctions as ContractArtifact,
|
||||
};
|
||||
|
@@ -30,6 +30,7 @@ export * from '../generated-wrappers/test_asset_proxy_dispatcher';
|
||||
export * from '../generated-wrappers/test_exchange_internals';
|
||||
export * from '../generated-wrappers/test_lib_exchange_rich_error_decoder';
|
||||
export * from '../generated-wrappers/test_signature_validator';
|
||||
export * from '../generated-wrappers/test_transactions';
|
||||
export * from '../generated-wrappers/test_validator_wallet';
|
||||
export * from '../generated-wrappers/test_wrapper_functions';
|
||||
export * from '../generated-wrappers/whitelist';
|
||||
|
@@ -177,6 +177,48 @@ blockchainTests.resets('Exchange transactions', env => {
|
||||
const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if the actual gasPrice is greater than expected', async () => {
|
||||
const order = await orderFactory.newSignedOrderAsync();
|
||||
const orders = [order];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({
|
||||
data,
|
||||
});
|
||||
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const actualGasPrice = transaction.gasPrice.plus(1);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionGasPriceError(
|
||||
transactionHashHex,
|
||||
actualGasPrice,
|
||||
transaction.gasPrice,
|
||||
);
|
||||
const tx = exchangeInstance.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
transaction.signature,
|
||||
{ gasPrice: actualGasPrice, from: senderAddress },
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if the actual gasPrice is less than expected', async () => {
|
||||
const order = await orderFactory.newSignedOrderAsync();
|
||||
const orders = [order];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({
|
||||
data,
|
||||
});
|
||||
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const actualGasPrice = transaction.gasPrice.minus(1);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionGasPriceError(
|
||||
transactionHashHex,
|
||||
actualGasPrice,
|
||||
transaction.gasPrice,
|
||||
);
|
||||
const tx = exchangeInstance.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
transaction.signature,
|
||||
{ gasPrice: actualGasPrice, from: senderAddress },
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
describe('fill methods', () => {
|
||||
for (const fnName of [
|
||||
@@ -307,9 +349,9 @@ blockchainTests.resets('Exchange transactions', env => {
|
||||
const recursiveTransactionHashHex = transactionHashUtils.getTransactionHashHex(
|
||||
recursiveTransaction,
|
||||
);
|
||||
const noReentrancyError = new ExchangeRevertErrors.TransactionError(
|
||||
ExchangeRevertErrors.TransactionErrorCode.NoReentrancy,
|
||||
const noReentrancyError = new ExchangeRevertErrors.TransactionInvalidContextError(
|
||||
transactionHashHex,
|
||||
transaction.signerAddress,
|
||||
).encode();
|
||||
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
|
||||
recursiveTransactionHashHex,
|
||||
|
715
contracts/exchange/test/transactions_unit_tests.ts
Normal file
715
contracts/exchange/test/transactions_unit_tests.ts
Normal file
@@ -0,0 +1,715 @@
|
||||
import { blockchainTests, constants, describe, expect, hexRandom, TransactionHelper } from '@0x/contracts-test-utils';
|
||||
import { ExchangeRevertErrors, transactionHashUtils } from '@0x/order-utils';
|
||||
import { EIP712DomainWithDefaultSchema, ZeroExTransaction } from '@0x/types';
|
||||
import { BigNumber, StringRevertError } from '@0x/utils';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts, TestTransactionsContract, TestTransactionsTransactionExecutionEventArgs } from '../src';
|
||||
|
||||
blockchainTests.resets('Transaction Unit Tests', ({ provider, web3Wrapper, txDefaults }) => {
|
||||
let transactionsContract: TestTransactionsContract;
|
||||
let accounts: string[];
|
||||
let domain: EIP712DomainWithDefaultSchema;
|
||||
|
||||
const randomSignature = () => hexRandom(66);
|
||||
|
||||
const EMPTY_ZERO_EX_TRANSACTION = {
|
||||
salt: constants.ZERO_AMOUNT,
|
||||
expirationTimeSeconds: constants.ZERO_AMOUNT,
|
||||
gasPrice: constants.ZERO_AMOUNT,
|
||||
signerAddress: constants.NULL_ADDRESS,
|
||||
data: constants.NULL_BYTES,
|
||||
domain: {
|
||||
verifyingContractAddress: constants.NULL_ADDRESS,
|
||||
chainId: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const DEADBEEF_RETURN_DATA = '0xdeadbeef';
|
||||
const INVALID_SIGNATURE = '0x0000';
|
||||
|
||||
const transactionHelper = new TransactionHelper(web3Wrapper, artifacts);
|
||||
|
||||
before(async () => {
|
||||
// A list of available addresses to use during testing.
|
||||
accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
|
||||
// Deploy the transaction test contract.
|
||||
transactionsContract = await TestTransactionsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestTransactions,
|
||||
provider,
|
||||
txDefaults,
|
||||
{},
|
||||
);
|
||||
|
||||
// Set the default domain.
|
||||
domain = {
|
||||
verifyingContractAddress: transactionsContract.address,
|
||||
chainId: 1337,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Generates calldata for a call to `executable()` in the `TestTransactions` contract.
|
||||
*/
|
||||
function getExecutableCallData(shouldSucceed: boolean, callData: string, returnData: string): string {
|
||||
return (transactionsContract as any).executable.getABIEncodedTransactionData(
|
||||
shouldSucceed,
|
||||
callData,
|
||||
returnData,
|
||||
);
|
||||
}
|
||||
|
||||
interface GenerateZeroExTransactionParams {
|
||||
salt?: BigNumber;
|
||||
expirationTimeSeconds?: BigNumber;
|
||||
gasPrice?: BigNumber;
|
||||
signerAddress?: string;
|
||||
data?: string;
|
||||
domain?: EIP712DomainWithDefaultSchema;
|
||||
shouldSucceed?: boolean;
|
||||
callData?: string;
|
||||
returnData?: string;
|
||||
}
|
||||
|
||||
async function generateZeroExTransactionAsync(
|
||||
opts: GenerateZeroExTransactionParams = {},
|
||||
): Promise<ZeroExTransaction> {
|
||||
const shouldSucceed = opts.shouldSucceed === undefined ? true : opts.shouldSucceed;
|
||||
const callData = opts.callData === undefined ? constants.NULL_BYTES : opts.callData;
|
||||
const returnData = opts.returnData === undefined ? constants.NULL_BYTES : opts.returnData;
|
||||
const data = opts.data === undefined ? getExecutableCallData(shouldSucceed, callData, returnData) : opts.data;
|
||||
const gasPrice = opts.gasPrice === undefined ? new BigNumber(constants.DEFAULT_GAS_PRICE) : opts.gasPrice;
|
||||
const _domain = opts.domain === undefined ? domain : opts.domain;
|
||||
const expirationTimeSeconds =
|
||||
opts.expirationTimeSeconds === undefined ? constants.MAX_UINT256 : opts.expirationTimeSeconds;
|
||||
const transaction = {
|
||||
...EMPTY_ZERO_EX_TRANSACTION,
|
||||
...opts,
|
||||
data,
|
||||
expirationTimeSeconds,
|
||||
domain: _domain,
|
||||
gasPrice,
|
||||
};
|
||||
return transaction;
|
||||
}
|
||||
|
||||
describe('batchExecuteTransaction', () => {
|
||||
it('should revert if the only call to executeTransaction fails', async () => {
|
||||
// Create an expired transaction that will fail when used to call `batchExecuteTransactions()`.
|
||||
const transaction = await generateZeroExTransactionAsync({ shouldSucceed: false });
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
|
||||
// Create the StringRevertError that reflects the returndata that will be returned by the failed transaction.
|
||||
const executableError = new StringRevertError('EXECUTABLE_FAILED');
|
||||
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
|
||||
transactionHash,
|
||||
executableError.encode(),
|
||||
);
|
||||
|
||||
// Call the `batchExecuteTransactions()` function and ensure that it reverts with the expected revert error.
|
||||
const tx = transactionsContract.batchExecuteTransactions.awaitTransactionSuccessAsync(
|
||||
[transaction],
|
||||
[randomSignature()],
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('should revert if the second call to executeTransaction fails', async () => {
|
||||
// Create a transaction that will succeed when used to call `batchExecuteTransactions()`.
|
||||
const transaction1 = await generateZeroExTransactionAsync();
|
||||
|
||||
// Create a transaction that will fail when used to call `batchExecuteTransactions()` because the call to executable will fail.
|
||||
const transaction2 = await generateZeroExTransactionAsync({ shouldSucceed: false });
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction2);
|
||||
|
||||
// Create the StringRevertError that reflects the returndata that will be returned by the failed transaction.
|
||||
const executableError = new StringRevertError('EXECUTABLE_FAILED');
|
||||
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
|
||||
transactionHash,
|
||||
executableError.encode(),
|
||||
);
|
||||
|
||||
// Call the `batchExecuteTransactions()` function and ensure that it reverts with the expected revert error.
|
||||
const tx = transactionsContract.batchExecuteTransactions.awaitTransactionSuccessAsync(
|
||||
[transaction1, transaction2],
|
||||
[randomSignature(), randomSignature()],
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('should revert if the first call to executeTransaction fails', async () => {
|
||||
// Create a transaction that will fail when used to call `batchExecuteTransactions()` because the call to executable will fail.
|
||||
const transaction1 = await generateZeroExTransactionAsync({ shouldSucceed: false });
|
||||
|
||||
// Create a transaction that will succeed when used to call `batchExecuteTransactions()`.
|
||||
const transaction2 = await generateZeroExTransactionAsync();
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction1);
|
||||
|
||||
// Create the StringRevertError that reflects the returndata that will be returned by the failed transaction.
|
||||
const executableError = new StringRevertError('EXECUTABLE_FAILED');
|
||||
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
|
||||
transactionHash,
|
||||
executableError.encode(),
|
||||
);
|
||||
|
||||
// Call the `batchExecuteTransactions()` function and ensure that it reverts with the expected revert error.
|
||||
const tx = transactionsContract.batchExecuteTransactions.awaitTransactionSuccessAsync(
|
||||
[transaction1, transaction2],
|
||||
[randomSignature(), randomSignature()],
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('should revert if the same transaction is executed twice in a batch', async () => {
|
||||
// Create a transaction that will succeed when used to call `batchExecuteTransactions()`.
|
||||
const transaction1 = await generateZeroExTransactionAsync({ signerAddress: accounts[1] });
|
||||
|
||||
// Duplicate the first transaction. This should cause the call to `batchExecuteTransactions()` to fail
|
||||
// because this transaction will have the same order hash as transaction1.
|
||||
const transaction2 = transaction1;
|
||||
const transactionHash2 = transactionHashUtils.getTransactionHashHex(transaction2);
|
||||
|
||||
// Call the `batchExecuteTransactions()` function and ensure that it reverts with the expected revert error.
|
||||
const expectedError = new ExchangeRevertErrors.TransactionError(
|
||||
ExchangeRevertErrors.TransactionErrorCode.AlreadyExecuted,
|
||||
transactionHash2,
|
||||
);
|
||||
const tx = transactionsContract.batchExecuteTransactions.awaitTransactionSuccessAsync(
|
||||
[transaction1, transaction2],
|
||||
[randomSignature(), randomSignature()],
|
||||
{
|
||||
from: accounts[0],
|
||||
},
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('should succeed if the only call to executeTransaction succeeds', async () => {
|
||||
// Create a transaction that will succeed when used to call `batchExecuteTransactions()`.
|
||||
const transaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[1],
|
||||
returnData: DEADBEEF_RETURN_DATA,
|
||||
});
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const validSignature = randomSignature();
|
||||
|
||||
const [result, receipt] = await transactionHelper.getResultAndReceiptAsync(
|
||||
transactionsContract.batchExecuteTransactions,
|
||||
[transaction],
|
||||
[validSignature],
|
||||
{ from: accounts[0] },
|
||||
);
|
||||
|
||||
expect(result.length).to.be.eq(1);
|
||||
const returnData = transactionsContract.executeTransaction.getABIDecodedReturnData(result[0]);
|
||||
expect(returnData).to.equal(DEADBEEF_RETURN_DATA);
|
||||
|
||||
// Ensure that the correct number of events were logged.
|
||||
const logs = receipt.logs as Array<LogWithDecodedArgs<TestTransactionsTransactionExecutionEventArgs>>;
|
||||
expect(logs.length).to.be.eq(2);
|
||||
|
||||
// Ensure that the correct events were logged.
|
||||
expect(logs[0].event).to.be.eq('ExecutableCalled');
|
||||
expect(logs[0].args.data).to.be.eq(constants.NULL_BYTES);
|
||||
expect(logs[0].args.contextAddress).to.be.eq(accounts[1]);
|
||||
expect(logs[0].args.returnData).to.be.eq(DEADBEEF_RETURN_DATA);
|
||||
expect(logs[1].event).to.be.eq('TransactionExecution');
|
||||
expect(logs[1].args.transactionHash).to.eq(transactionHash);
|
||||
});
|
||||
|
||||
it('should succeed if the both calls to executeTransaction succeed', async () => {
|
||||
// Create two transactions that will succeed when used to call `batchExecuteTransactions()`.
|
||||
const transaction1 = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[0],
|
||||
returnData: DEADBEEF_RETURN_DATA,
|
||||
});
|
||||
const returnData2 = '0xbeefdead';
|
||||
const transaction2 = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[1],
|
||||
returnData: returnData2,
|
||||
});
|
||||
const transactionHash1 = transactionHashUtils.getTransactionHashHex(transaction1);
|
||||
const transactionHash2 = transactionHashUtils.getTransactionHashHex(transaction2);
|
||||
|
||||
const [result, receipt] = await transactionHelper.getResultAndReceiptAsync(
|
||||
transactionsContract.batchExecuteTransactions,
|
||||
[transaction1, transaction2],
|
||||
[randomSignature(), randomSignature()],
|
||||
{ from: accounts[0] },
|
||||
);
|
||||
|
||||
expect(result.length).to.be.eq(2);
|
||||
expect(transactionsContract.executeTransaction.getABIDecodedReturnData(result[0])).to.equal(
|
||||
DEADBEEF_RETURN_DATA,
|
||||
);
|
||||
expect(transactionsContract.executeTransaction.getABIDecodedReturnData(result[1])).to.equal(returnData2);
|
||||
|
||||
// Verify that the correct number of events were logged.
|
||||
const logs = receipt.logs as Array<LogWithDecodedArgs<TestTransactionsTransactionExecutionEventArgs>>;
|
||||
expect(logs.length).to.be.eq(4);
|
||||
|
||||
// Ensure that the correct events were logged.
|
||||
expect(logs[0].event).to.be.eq('ExecutableCalled');
|
||||
expect(logs[0].args.data).to.be.eq(constants.NULL_BYTES);
|
||||
expect(logs[0].args.returnData).to.be.eq(DEADBEEF_RETURN_DATA);
|
||||
expect(logs[0].args.contextAddress).to.be.eq(constants.NULL_ADDRESS);
|
||||
expect(logs[1].event).to.be.eq('TransactionExecution');
|
||||
expect(logs[1].args.transactionHash).to.eq(transactionHash1);
|
||||
expect(logs[2].event).to.be.eq('ExecutableCalled');
|
||||
expect(logs[2].args.data).to.be.eq(constants.NULL_BYTES);
|
||||
expect(logs[2].args.returnData).to.be.eq('0xbeefdead');
|
||||
expect(logs[2].args.contextAddress).to.be.eq(accounts[1]);
|
||||
expect(logs[3].event).to.be.eq('TransactionExecution');
|
||||
expect(logs[3].args.transactionHash).to.eq(transactionHash2);
|
||||
});
|
||||
it('should not allow recursion if currentContextAddress is already set', async () => {
|
||||
const innerTransaction1 = await generateZeroExTransactionAsync({ signerAddress: accounts[0] });
|
||||
const innerTransaction2 = await generateZeroExTransactionAsync({ signerAddress: accounts[1] });
|
||||
const innerBatchExecuteTransaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[2],
|
||||
callData: transactionsContract.batchExecuteTransactions.getABIEncodedTransactionData(
|
||||
[innerTransaction1, innerTransaction2],
|
||||
[randomSignature(), randomSignature()],
|
||||
),
|
||||
});
|
||||
const outerExecuteTransaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[1],
|
||||
callData: transactionsContract.executeTransaction.getABIEncodedTransactionData(
|
||||
innerBatchExecuteTransaction,
|
||||
randomSignature(),
|
||||
),
|
||||
});
|
||||
const innerBatchExecuteTransactionHash = transactionHashUtils.getTransactionHashHex(
|
||||
innerBatchExecuteTransaction,
|
||||
);
|
||||
const innerExpectedError = new ExchangeRevertErrors.TransactionInvalidContextError(
|
||||
innerBatchExecuteTransactionHash,
|
||||
accounts[1],
|
||||
);
|
||||
const outerExecuteTransactionHash = transactionHashUtils.getTransactionHashHex(outerExecuteTransaction);
|
||||
const outerExpectedError = new ExchangeRevertErrors.TransactionExecutionError(
|
||||
outerExecuteTransactionHash,
|
||||
innerExpectedError.encode(),
|
||||
);
|
||||
const tx = transactionsContract.batchExecuteTransactions.awaitTransactionSuccessAsync(
|
||||
[outerExecuteTransaction],
|
||||
[randomSignature()],
|
||||
{ from: accounts[2] },
|
||||
);
|
||||
return expect(tx).to.revertWith(outerExpectedError);
|
||||
});
|
||||
it('should allow recursion as long as currentContextAddress is not set', async () => {
|
||||
const innerTransaction1 = await generateZeroExTransactionAsync({ signerAddress: accounts[0] });
|
||||
const innerTransaction2 = await generateZeroExTransactionAsync({ signerAddress: accounts[1] });
|
||||
// From this point on, all transactions and calls will have the same sender, which does not change currentContextAddress when called
|
||||
const innerBatchExecuteTransaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[2],
|
||||
callData: transactionsContract.batchExecuteTransactions.getABIEncodedTransactionData(
|
||||
[innerTransaction1, innerTransaction2],
|
||||
[randomSignature(), randomSignature()],
|
||||
),
|
||||
});
|
||||
const outerExecuteTransaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[2],
|
||||
callData: transactionsContract.executeTransaction.getABIEncodedTransactionData(
|
||||
innerBatchExecuteTransaction,
|
||||
randomSignature(),
|
||||
),
|
||||
});
|
||||
return expect(
|
||||
transactionsContract.batchExecuteTransactions.awaitTransactionSuccessAsync(
|
||||
[outerExecuteTransaction],
|
||||
[randomSignature()],
|
||||
{ from: accounts[2] },
|
||||
),
|
||||
).to.be.fulfilled('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('executeTransaction', () => {
|
||||
function getExecuteTransactionCallData(transaction: ZeroExTransaction, signature: string): string {
|
||||
return (transactionsContract as any).executeTransaction.getABIEncodedTransactionData(
|
||||
transaction,
|
||||
signature,
|
||||
);
|
||||
}
|
||||
it('should revert if the current time is past the expiration time', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync({
|
||||
expirationTimeSeconds: constants.ZERO_AMOUNT,
|
||||
});
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionError(
|
||||
ExchangeRevertErrors.TransactionErrorCode.Expired,
|
||||
transactionHash,
|
||||
);
|
||||
const tx = transactionsContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
transaction,
|
||||
randomSignature(),
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if the transaction is submitted with a gasPrice that does not equal the required gasPrice', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync();
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const actualGasPrice = transaction.gasPrice.plus(1);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionGasPriceError(
|
||||
transactionHash,
|
||||
actualGasPrice,
|
||||
transaction.gasPrice,
|
||||
);
|
||||
const tx = transactionsContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
transaction,
|
||||
randomSignature(),
|
||||
{
|
||||
gasPrice: actualGasPrice,
|
||||
},
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if reentrancy occurs in the middle of an executeTransaction call and msg.sender != signer for both calls', async () => {
|
||||
const validSignature = randomSignature();
|
||||
const innerTransaction = await generateZeroExTransactionAsync({ signerAddress: accounts[0] });
|
||||
const innerTransactionHash = transactionHashUtils.getTransactionHashHex(innerTransaction);
|
||||
const outerTransaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[0],
|
||||
callData: getExecuteTransactionCallData(innerTransaction, validSignature),
|
||||
returnData: DEADBEEF_RETURN_DATA,
|
||||
});
|
||||
const outerTransactionHash = transactionHashUtils.getTransactionHashHex(outerTransaction);
|
||||
const errorData = new ExchangeRevertErrors.TransactionInvalidContextError(
|
||||
innerTransactionHash,
|
||||
accounts[0],
|
||||
).encode();
|
||||
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(outerTransactionHash, errorData);
|
||||
const tx = transactionsContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
outerTransaction,
|
||||
validSignature,
|
||||
{
|
||||
from: accounts[1], // Different then the signing addresses
|
||||
},
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if reentrancy occurs in the middle of an executeTransaction call and msg.sender != signer and then msg.sender == signer', async () => {
|
||||
const validSignature = randomSignature();
|
||||
const innerTransaction = await generateZeroExTransactionAsync({ signerAddress: accounts[1] });
|
||||
const innerTransactionHash = transactionHashUtils.getTransactionHashHex(innerTransaction);
|
||||
const outerTransaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[0],
|
||||
callData: getExecuteTransactionCallData(innerTransaction, validSignature),
|
||||
returnData: DEADBEEF_RETURN_DATA,
|
||||
});
|
||||
const outerTransactionHash = transactionHashUtils.getTransactionHashHex(outerTransaction);
|
||||
const errorData = new ExchangeRevertErrors.TransactionInvalidContextError(
|
||||
innerTransactionHash,
|
||||
accounts[0],
|
||||
).encode();
|
||||
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(outerTransactionHash, errorData);
|
||||
const tx = transactionsContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
outerTransaction,
|
||||
validSignature,
|
||||
{
|
||||
from: accounts[1], // Different then the signing addresses
|
||||
},
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should allow reentrancy in the middle of an executeTransaction call if msg.sender == signer for both calls', async () => {
|
||||
const validSignature = randomSignature();
|
||||
const innerTransaction = await generateZeroExTransactionAsync({ signerAddress: accounts[0] });
|
||||
const outerTransaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[0],
|
||||
callData: getExecuteTransactionCallData(innerTransaction, validSignature),
|
||||
returnData: DEADBEEF_RETURN_DATA,
|
||||
});
|
||||
return expect(
|
||||
transactionsContract.executeTransaction.awaitTransactionSuccessAsync(outerTransaction, validSignature, {
|
||||
from: accounts[0],
|
||||
}),
|
||||
).to.be.fulfilled('');
|
||||
});
|
||||
it('should allow reentrancy in the middle of an executeTransaction call if msg.sender == signer and then msg.sender != signer', async () => {
|
||||
const validSignature = randomSignature();
|
||||
const innerTransaction = await generateZeroExTransactionAsync({ signerAddress: accounts[1] });
|
||||
const outerTransaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[0],
|
||||
callData: getExecuteTransactionCallData(innerTransaction, validSignature),
|
||||
returnData: DEADBEEF_RETURN_DATA,
|
||||
});
|
||||
return expect(
|
||||
transactionsContract.executeTransaction.awaitTransactionSuccessAsync(outerTransaction, validSignature, {
|
||||
from: accounts[0],
|
||||
}),
|
||||
).to.be.fulfilled('');
|
||||
});
|
||||
it('should revert if the transaction has been executed previously', async () => {
|
||||
const validSignature = randomSignature();
|
||||
const transaction = await generateZeroExTransactionAsync();
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
// Use the transaction in execute transaction.
|
||||
await expect(
|
||||
transactionsContract.executeTransaction.awaitTransactionSuccessAsync(transaction, validSignature),
|
||||
).to.be.fulfilled('');
|
||||
// Use the same transaction to make another call
|
||||
const expectedError = new ExchangeRevertErrors.TransactionError(
|
||||
ExchangeRevertErrors.TransactionErrorCode.AlreadyExecuted,
|
||||
transactionHash,
|
||||
);
|
||||
const tx = transactionsContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
transaction,
|
||||
validSignature,
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if the signer != msg.sender and the signature is not valid', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync({ signerAddress: accounts[1] });
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionSignatureError(
|
||||
transactionHash,
|
||||
accounts[1],
|
||||
INVALID_SIGNATURE,
|
||||
);
|
||||
const tx = transactionsContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
transaction,
|
||||
INVALID_SIGNATURE,
|
||||
{
|
||||
from: accounts[0],
|
||||
},
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if the signer == msg.sender but the delegatecall fails', async () => {
|
||||
// This calldata is encoded to fail when it hits the executable function.
|
||||
const transaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[1],
|
||||
shouldSucceed: false,
|
||||
});
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const executableError = new StringRevertError('EXECUTABLE_FAILED');
|
||||
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
|
||||
transactionHash,
|
||||
executableError.encode(),
|
||||
);
|
||||
const tx = transactionsContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
transaction,
|
||||
randomSignature(),
|
||||
{
|
||||
from: accounts[1],
|
||||
},
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if the signer != msg.sender and the signature is valid but the delegatecall fails', async () => {
|
||||
// This calldata is encoded to fail when it hits the executable function.
|
||||
const transaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[1],
|
||||
shouldSucceed: false,
|
||||
});
|
||||
const validSignature = randomSignature(); // Valid because length != 2
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const executableError = new StringRevertError('EXECUTABLE_FAILED');
|
||||
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
|
||||
transactionHash,
|
||||
executableError.encode(),
|
||||
);
|
||||
const tx = transactionsContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
transaction,
|
||||
validSignature,
|
||||
{
|
||||
from: accounts[0],
|
||||
},
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('should succeed with the correct return hash and event emitted when msg.sender != signer', async () => {
|
||||
// This calldata is encoded to succeed when it hits the executable function.
|
||||
const validSignature = randomSignature(); // Valid because length != 2
|
||||
const transaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[1],
|
||||
returnData: DEADBEEF_RETURN_DATA,
|
||||
});
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
|
||||
const [result, receipt] = await transactionHelper.getResultAndReceiptAsync(
|
||||
transactionsContract.executeTransaction,
|
||||
transaction,
|
||||
validSignature,
|
||||
{ from: accounts[0] },
|
||||
);
|
||||
|
||||
expect(transactionsContract.executeTransaction.getABIDecodedReturnData(result)).to.equal(
|
||||
DEADBEEF_RETURN_DATA,
|
||||
);
|
||||
|
||||
// Ensure that the correct number of events were logged.
|
||||
const logs = receipt.logs as Array<LogWithDecodedArgs<TestTransactionsTransactionExecutionEventArgs>>;
|
||||
expect(logs.length).to.be.eq(2);
|
||||
// Ensure that the correct events were logged.
|
||||
expect(logs[0].event).to.be.eq('ExecutableCalled');
|
||||
expect(logs[0].args.data).to.be.eq(constants.NULL_BYTES);
|
||||
expect(logs[0].args.returnData).to.be.eq(DEADBEEF_RETURN_DATA);
|
||||
expect(logs[0].args.contextAddress).to.be.eq(accounts[1]);
|
||||
expect(logs[1].event).to.be.eq('TransactionExecution');
|
||||
expect(logs[1].args.transactionHash).to.eq(transactionHash);
|
||||
});
|
||||
it('should succeed with the correct return hash and event emitted when msg.sender == signer', async () => {
|
||||
// This calldata is encoded to succeed when it hits the executable function.
|
||||
const validSignature = randomSignature(); // Valid because length != 2
|
||||
const transaction = await generateZeroExTransactionAsync({
|
||||
signerAddress: accounts[0],
|
||||
returnData: DEADBEEF_RETURN_DATA,
|
||||
});
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
|
||||
const [result, receipt] = await transactionHelper.getResultAndReceiptAsync(
|
||||
transactionsContract.executeTransaction,
|
||||
transaction,
|
||||
validSignature,
|
||||
{ from: accounts[0] },
|
||||
);
|
||||
|
||||
expect(transactionsContract.executeTransaction.getABIDecodedReturnData(result)).to.equal(
|
||||
DEADBEEF_RETURN_DATA,
|
||||
);
|
||||
|
||||
// Ensure that the correct number of events were logged.
|
||||
const logs = receipt.logs as Array<LogWithDecodedArgs<TestTransactionsTransactionExecutionEventArgs>>;
|
||||
expect(logs.length).to.be.eq(2);
|
||||
// Ensure that the correct events were logged.
|
||||
expect(logs[0].event).to.be.eq('ExecutableCalled');
|
||||
expect(logs[0].args.data).to.be.eq(constants.NULL_BYTES);
|
||||
expect(logs[0].args.returnData).to.be.eq(DEADBEEF_RETURN_DATA);
|
||||
expect(logs[0].args.contextAddress).to.be.eq(constants.NULL_ADDRESS);
|
||||
expect(logs[1].event).to.be.eq('TransactionExecution');
|
||||
expect(logs[1].args.transactionHash).to.eq(transactionHash);
|
||||
});
|
||||
});
|
||||
|
||||
blockchainTests.resets('assertExecutableTransaction', () => {
|
||||
it('should revert if the transaction is expired', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync({
|
||||
expirationTimeSeconds: constants.ZERO_AMOUNT,
|
||||
});
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionError(
|
||||
ExchangeRevertErrors.TransactionErrorCode.Expired,
|
||||
transactionHash,
|
||||
);
|
||||
expect(
|
||||
transactionsContract.assertExecutableTransaction.callAsync(transaction, randomSignature()),
|
||||
).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if the gasPrice is less than required', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync();
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const actualGasPrice = transaction.gasPrice.minus(1);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionGasPriceError(
|
||||
transactionHash,
|
||||
actualGasPrice,
|
||||
transaction.gasPrice,
|
||||
);
|
||||
expect(
|
||||
transactionsContract.assertExecutableTransaction.callAsync(transaction, randomSignature(), {
|
||||
gasPrice: actualGasPrice,
|
||||
}),
|
||||
).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if the gasPrice is greater than required', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync();
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const actualGasPrice = transaction.gasPrice.plus(1);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionGasPriceError(
|
||||
transactionHash,
|
||||
actualGasPrice,
|
||||
transaction.gasPrice,
|
||||
);
|
||||
expect(
|
||||
transactionsContract.assertExecutableTransaction.callAsync(transaction, randomSignature(), {
|
||||
gasPrice: actualGasPrice,
|
||||
}),
|
||||
).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if currentContextAddress is non-zero', async () => {
|
||||
await transactionsContract.setCurrentContextAddress.awaitTransactionSuccessAsync(accounts[0]);
|
||||
const transaction = await generateZeroExTransactionAsync();
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionInvalidContextError(transactionHash, accounts[0]);
|
||||
expect(
|
||||
transactionsContract.assertExecutableTransaction.callAsync(transaction, randomSignature()),
|
||||
).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if the transaction has already been executed', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync();
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
await transactionsContract.setTransactionExecuted.awaitTransactionSuccessAsync(transactionHash);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionError(
|
||||
ExchangeRevertErrors.TransactionErrorCode.AlreadyExecuted,
|
||||
transactionHash,
|
||||
);
|
||||
expect(
|
||||
transactionsContract.assertExecutableTransaction.callAsync(transaction, randomSignature()),
|
||||
).to.revertWith(expectedError);
|
||||
});
|
||||
it('should revert if signer != msg.sender and the signature is invalid', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync({ signerAddress: accounts[0] });
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const expectedError = new ExchangeRevertErrors.TransactionSignatureError(
|
||||
transactionHash,
|
||||
accounts[0],
|
||||
INVALID_SIGNATURE,
|
||||
);
|
||||
expect(
|
||||
transactionsContract.assertExecutableTransaction.callAsync(transaction, INVALID_SIGNATURE, {
|
||||
from: accounts[1],
|
||||
}),
|
||||
).to.revertWith(expectedError);
|
||||
});
|
||||
it('should be successful if signer == msg.sender and the signature is invalid', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync({ signerAddress: accounts[0] });
|
||||
return expect(
|
||||
transactionsContract.assertExecutableTransaction.callAsync(transaction, INVALID_SIGNATURE, {
|
||||
from: accounts[0],
|
||||
}),
|
||||
).to.be.fulfilled('');
|
||||
});
|
||||
it('should be successful if signer == msg.sender and the signature is valid', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync({ signerAddress: accounts[0] });
|
||||
return expect(
|
||||
transactionsContract.assertExecutableTransaction.callAsync(transaction, randomSignature(), {
|
||||
from: accounts[0],
|
||||
}),
|
||||
).to.be.fulfilled('');
|
||||
});
|
||||
it('should be successful if not expired, the gasPrice is correct, the tx has not been executed, currentContextAddress has not been set, signer != msg.sender, and the signature is valid', async () => {
|
||||
const transaction = await generateZeroExTransactionAsync({ signerAddress: accounts[0] });
|
||||
return expect(
|
||||
transactionsContract.assertExecutableTransaction.callAsync(transaction, randomSignature(), {
|
||||
from: accounts[1],
|
||||
}),
|
||||
).to.be.fulfilled('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCurrentContext', () => {
|
||||
it('should return the sender address when there is not a saved context address', async () => {
|
||||
const currentContextAddress = await transactionsContract.getCurrentContextAddress.callAsync({
|
||||
from: accounts[0],
|
||||
});
|
||||
expect(currentContextAddress).to.be.eq(accounts[0]);
|
||||
});
|
||||
|
||||
it('should return the sender address when there is a saved context address', async () => {
|
||||
// Set the current context address to the taker address
|
||||
await transactionsContract.setCurrentContextAddress.awaitTransactionSuccessAsync(accounts[1]);
|
||||
|
||||
// Ensure that the queried current context address is the same as the address that was set.
|
||||
const currentContextAddress = await transactionsContract.getCurrentContextAddress.callAsync({
|
||||
from: accounts[0],
|
||||
});
|
||||
expect(currentContextAddress).to.be.eq(accounts[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable-line:max-file-line-count
|
@@ -30,6 +30,7 @@
|
||||
"generated-artifacts/TestExchangeInternals.json",
|
||||
"generated-artifacts/TestLibExchangeRichErrorDecoder.json",
|
||||
"generated-artifacts/TestSignatureValidator.json",
|
||||
"generated-artifacts/TestTransactions.json",
|
||||
"generated-artifacts/TestValidatorWallet.json",
|
||||
"generated-artifacts/TestWrapperFunctions.json",
|
||||
"generated-artifacts/Whitelist.json"
|
||||
|
@@ -68,4 +68,5 @@ export const constants = {
|
||||
ONE_ETHER: new BigNumber(1e18),
|
||||
EIP712_DOMAIN_NAME: '0x Protocol',
|
||||
EIP712_DOMAIN_VERSION: '3.0.0',
|
||||
DEFAULT_GAS_PRICE: 1,
|
||||
};
|
||||
|
@@ -4,6 +4,7 @@ import { BigNumber } from '@0x/utils';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
|
||||
import { getLatestBlockTimestampAsync } from './block_timestamp';
|
||||
import { constants } from './constants';
|
||||
import { signingUtils } from './signing_utils';
|
||||
|
||||
export class TransactionFactory {
|
||||
@@ -34,6 +35,7 @@ export class TransactionFactory {
|
||||
signerAddress,
|
||||
data: customTransactionParams.data,
|
||||
expirationTimeSeconds: new BigNumber(currentBlockTimestamp).plus(tenMinutesInSeconds),
|
||||
gasPrice: new BigNumber(constants.DEFAULT_GAS_PRICE),
|
||||
domain: {
|
||||
verifyingContractAddress: this._exchangeAddress,
|
||||
chainId: this._chainId,
|
||||
|
@@ -4,6 +4,7 @@ import { logUtils } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from './constants';
|
||||
import { coverage } from './coverage';
|
||||
import { profiler } from './profiler';
|
||||
import { revertTrace } from './revert_trace';
|
||||
@@ -31,6 +32,7 @@ switch (process.env.TEST_PROVIDER) {
|
||||
const ganacheTxDefaults = {
|
||||
from: devConstants.TESTRPC_FIRST_ADDRESS,
|
||||
gas: devConstants.GAS_LIMIT,
|
||||
gasPrice: constants.DEFAULT_GAS_PRICE,
|
||||
};
|
||||
const gethTxDefaults = {
|
||||
from: devConstants.TESTRPC_FIRST_ADDRESS,
|
||||
|
@@ -5,8 +5,9 @@
|
||||
"signerAddress": { "$ref": "/addressSchema" },
|
||||
"salt": { "$ref": "/wholeNumberSchema" },
|
||||
"expirationTimeSeconds": { "$ref": "/wholeNumberSchema" },
|
||||
"gasPrice": { "$ref": "/wholeNumberSchema" },
|
||||
"domain": { "$ref": "/eip712DomainSchema" }
|
||||
},
|
||||
"required": ["data", "salt", "expirationTimeSeconds", "signerAddress", "domain"],
|
||||
"required": ["data", "salt", "expirationTimeSeconds", "gasPrice", "signerAddress", "domain"],
|
||||
"type": "object"
|
||||
}
|
||||
|
@@ -134,6 +134,7 @@ export const constants = {
|
||||
parameters: [
|
||||
{ name: 'salt', type: 'uint256' },
|
||||
{ name: 'expirationTimeSeconds', type: 'uint256' },
|
||||
{ name: 'gasPrice', type: 'uint256' },
|
||||
{ name: 'signerAddress', type: 'address' },
|
||||
{ name: 'data', type: 'bytes' },
|
||||
],
|
||||
|
@@ -33,7 +33,6 @@ export enum AssetProxyDispatchErrorCode {
|
||||
}
|
||||
|
||||
export enum TransactionErrorCode {
|
||||
NoReentrancy,
|
||||
AlreadyExecuted,
|
||||
Expired,
|
||||
}
|
||||
@@ -136,7 +135,7 @@ export class FillError extends RevertError {
|
||||
}
|
||||
|
||||
export class OrderEpochError extends RevertError {
|
||||
constructor(maker?: string, sender?: string, currentEpoch?: BigNumber | number | string) {
|
||||
constructor(maker?: string, sender?: string, currentEpoch?: BigNumber) {
|
||||
super('OrderEpochError', 'OrderEpochError(address maker, address sender, uint256 currentEpoch)', {
|
||||
maker,
|
||||
sender,
|
||||
@@ -213,6 +212,33 @@ export class TransactionExecutionError extends RevertError {
|
||||
}
|
||||
}
|
||||
|
||||
export class TransactionGasPriceError extends RevertError {
|
||||
constructor(transactionHash?: string, actualGasPrice?: BigNumber, requiredGasPrice?: BigNumber) {
|
||||
super(
|
||||
'TransactionGasPriceError',
|
||||
'TransactionGasPriceError(bytes32 transactionHash, uint256 actualGasPrice, uint256 requiredGasPrice)',
|
||||
{
|
||||
transactionHash,
|
||||
actualGasPrice,
|
||||
requiredGasPrice,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class TransactionInvalidContextError extends RevertError {
|
||||
constructor(transactionHash?: string, currentContextAddress?: string) {
|
||||
super(
|
||||
'TransactionInvalidContextError',
|
||||
'TransactionInvalidContextError(bytes32 transactionHash, address currentContextAddress)',
|
||||
{
|
||||
transactionHash,
|
||||
currentContextAddress,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class IncompleteFillError extends RevertError {
|
||||
constructor(
|
||||
error?: IncompleteFillErrorCode,
|
||||
|
@@ -59,6 +59,7 @@ describe('EIP712 Utils', () => {
|
||||
const typedData = eip712Utils.createZeroExTransactionTypedData({
|
||||
salt: new BigNumber(0),
|
||||
expirationTimeSeconds: new BigNumber(0),
|
||||
gasPrice: new BigNumber(0),
|
||||
data: constants.NULL_BYTES,
|
||||
signerAddress: constants.NULL_ADDRESS,
|
||||
domain: {
|
||||
|
@@ -55,6 +55,7 @@ describe('Signature utils', () => {
|
||||
signerAddress: makerAddress,
|
||||
data: '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0',
|
||||
expirationTimeSeconds: new BigNumber(0),
|
||||
gasPrice: new BigNumber(0),
|
||||
};
|
||||
});
|
||||
describe('#isValidSignatureAsync', () => {
|
||||
|
@@ -14,13 +14,14 @@ const expect = chai.expect;
|
||||
|
||||
describe('0x transaction hashing', () => {
|
||||
describe('#getTransactionHashHex', () => {
|
||||
const expectedTransactionHash = '0x9779e4ca195f8c9c6f137f495599e9a1944806310b64748479bfa6c6b1ae7eb4';
|
||||
const expectedTransactionHash = '0x420b19f08d5b09c012f381f4bf80a97740b8629f2bac7f42dd7f6aefbb24f3c0';
|
||||
const fakeVerifyingContractAddress = '0x5e72914535f202659083db3a02c984188fa26e9f';
|
||||
const fakeChainId = 1337;
|
||||
const transaction: ZeroExTransaction = {
|
||||
signerAddress: constants.NULL_ADDRESS,
|
||||
salt: new BigNumber(0),
|
||||
expirationTimeSeconds: new BigNumber(0),
|
||||
gasPrice: new BigNumber(0),
|
||||
data: constants.NULL_BYTES,
|
||||
domain: {
|
||||
verifyingContractAddress: fakeVerifyingContractAddress,
|
||||
@@ -40,6 +41,7 @@ describe('0x transaction hashing', () => {
|
||||
...transaction,
|
||||
salt: '0',
|
||||
expirationTimeSeconds: '0',
|
||||
gasPrice: '0',
|
||||
} as any);
|
||||
expect(transactionHash).to.be.equal(expectedTransactionHash);
|
||||
});
|
||||
|
@@ -56,6 +56,7 @@ export enum MarketOperation {
|
||||
export interface ZeroExTransaction {
|
||||
salt: BigNumber;
|
||||
expirationTimeSeconds: BigNumber;
|
||||
gasPrice: BigNumber;
|
||||
signerAddress: string;
|
||||
data: string;
|
||||
domain: EIP712DomainWithDefaultSchema;
|
||||
|
Reference in New Issue
Block a user