Working towards maker signature validation

This commit is contained in:
Greg Hysen 2019-06-24 14:38:56 -07:00
parent 1f2e94b585
commit 36b76550e0
21 changed files with 462 additions and 65 deletions

View File

@ -54,6 +54,7 @@
"@0x/dev-utils": "^2.3.0", "@0x/dev-utils": "^2.3.0",
"@0x/sol-compiler": "^3.1.12", "@0x/sol-compiler": "^3.1.12",
"@0x/tslint-config": "^3.0.1", "@0x/tslint-config": "^3.0.1",
"@0x/utils": "^4.3.1",
"@types/lodash": "4.14.104", "@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7", "@types/mocha": "^5.2.7",
"@types/node": "*", "@types/node": "*",

View File

@ -24,6 +24,7 @@ import "../immutable/MixinConstants.sol";
import "../interfaces/IStakingEvents.sol"; import "../interfaces/IStakingEvents.sol";
import "./MixinRewardVault.sol"; import "./MixinRewardVault.sol";
import "./MixinSignatureValidator.sol"; import "./MixinSignatureValidator.sol";
import "../libs/LibEIP712Hash.sol";
contract MixinPools is contract MixinPools is
SafeMath, SafeMath,
@ -79,7 +80,7 @@ contract MixinPools is
); );
require( require(
_isValidSignature(hashSignedByMaker, makerAddress, makerSignature), _isValidMakerSignature(poolId, makerAddress, makerSignature),
"INVALID_MAKER_SIGNATURE" "INVALID_MAKER_SIGNATURE"
); );
@ -101,14 +102,32 @@ contract MixinPools is
_unrecordMaker(poolId, makerAddress); _unrecordMaker(poolId, makerAddress);
} }
/* function _isValidMakerSignature(bytes32 poolId, address makerAddress, bytes memory makerSignature)
function _isValidMakerSignature(address makerAddress, bytes memory makerSignature)
internal internal
view
returns (bool isValid) returns (bool isValid)
{ {
bytes32 approvalHash = _getStakingPoolApprovalMessageHash(poolId, makerAddress);
isValid = _isValidSignature(approvalHash, makerAddress, makerSignature);
return isValid;
}
function _getStakingPoolApprovalMessageHash(bytes32 poolId, address makerAddress)
internal
view
returns (bytes32 approvalHash)
{
StakingPoolApproval memory approval = StakingPoolApproval({
poolId: poolId,
makerAddress: makerAddress
});
// Hash approval message and check signer address
address verifierAddress = address(this);
approvalHash = LibEIP712Hash._hashStakingPoolApprovalMessage(approval, verifierAddress, CHAIN_ID);
return approvalHash;
} }
*/
function _getMakerPoolId(address makerAddress) function _getMakerPoolId(address makerAddress)
internal internal

View File

@ -16,10 +16,12 @@ pragma solidity ^0.5.5;
import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "../interfaces/IStructs.sol"; import "../interfaces/IStructs.sol";
import "../interfaces/IWallet.sol"; import "../interfaces/IWallet.sol";
import "../immutable/MixinConstants.sol";
contract MixinSignatureValidator is contract MixinSignatureValidator is
IStructs IStructs,
MixinConstants
{ {
using LibBytes for bytes; using LibBytes for bytes;
@ -121,17 +123,7 @@ contract MixinSignatureValidator is
// Signature verified by wallet contract. // Signature verified by wallet contract.
// If used with an order, the maker of the order is the wallet contract. // If used with an order, the maker of the order is the wallet contract.
} else if (signatureType == SignatureType.Wallet) { } else if (signatureType == SignatureType.Wallet) {
isValid = isValidWalletSignature( isValid = _isValidWalletSignature(
hash,
signerAddress,
signature
);
return isValid;
// Signature verified by wallet contract.
// If used with an order, the maker of the order is the wallet contract.
} else if (signatureType == SignatureType.Wallet) {
isValid = isValidWalletSignature(
hash, hash,
signerAddress, signerAddress,
signature signature
@ -153,7 +145,7 @@ contract MixinSignatureValidator is
/// and defines its own signature verification method. /// and defines its own signature verification method.
/// @param signature Proof that the hash has been signed by signer. /// @param signature Proof that the hash has been signed by signer.
/// @return True if signature is valid for given wallet.. /// @return True if signature is valid for given wallet..
function isValidWalletSignature( function _isValidWalletSignature(
bytes32 hash, bytes32 hash,
address walletAddress, address walletAddress,
bytes memory signature bytes memory signature
@ -162,36 +154,28 @@ contract MixinSignatureValidator is
view view
returns (bool isValid) returns (bool isValid)
{ {
// contruct hash as bytes, so that it is a valid EIP-1271 payload
bytes memory hashAsBytes = new bytes(32);
assembly {
mstore(add(hashAsBytes, 32), hash)
}
// Static call `isValidSignature` in the destination wallet
bytes memory callData = abi.encodeWithSelector( bytes memory callData = abi.encodeWithSelector(
IWallet(walletAddress).isValidSignature.selector, IWallet(walletAddress).isValidSignature.selector,
hash, hash,
signature signature
); );
assembly { (bool success, bytes memory result) = walletAddress.staticcall(callData);
let cdStart := add(callData, 32)
let success := staticcall(
gas, // forward all gas
walletAddress, // address of Wallet contract
cdStart, // pointer to start of input
mload(callData), // length of input
cdStart, // write output over input
32 // output size is 32 bytes
)
switch success // Sanity check call and extract the magic value
case 0 { require(
// Revert with `Error("WALLET_ERROR")` success,
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) "WALLET_ERROR"
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) );
mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000) bytes4 magicValue = result.readBytes4(0);
mstore(96, 0)
revert(0, 100) isValid = (magicValue == EIP1271_MAGIC_VALUE);
}
case 1 {
// Signature is valid if call did not revert and returned true
isValid := mload(cdStart)
}
}
return isValid; return isValid;
} }
} }

View File

@ -35,6 +35,9 @@ contract MixinConstants {
uint64 constant public INITIAL_TIMELOCK_PERIOD = INITIAL_EPOCH; uint64 constant public INITIAL_TIMELOCK_PERIOD = INITIAL_EPOCH;
// bytes4(keccak256("isValidSignature(bytes,bytes)")
bytes4 constant internal EIP1271_MAGIC_VALUE = 0x20c13b0b;
uint64 constant public EPOCH_PERIOD_IN_SECONDS = 1000; // @TODO SET FOR DEPLOYMENT uint64 constant public EPOCH_PERIOD_IN_SECONDS = 1000; // @TODO SET FOR DEPLOYMENT
uint64 constant public TIMELOCK_PERIOD_IN_EPOCHS = 3; // @TODO SET FOR DEPLOYMENT uint64 constant public TIMELOCK_PERIOD_IN_EPOCHS = 3; // @TODO SET FOR DEPLOYMENT
@ -42,4 +45,6 @@ contract MixinConstants {
uint256 constant public COBB_DOUGLAS_ALPHA_DENOMINATOR = 6; // @TODO SET FOR DEPLOYMENT uint256 constant public COBB_DOUGLAS_ALPHA_DENOMINATOR = 6; // @TODO SET FOR DEPLOYMENT
uint256 constant public REWARD_PAYOUT_DELEGATED_STAKE_PERCENT_VALUE = 90; // @TODO SET FOR DEPLOYMENT uint256 constant public REWARD_PAYOUT_DELEGATED_STAKE_PERCENT_VALUE = 90; // @TODO SET FOR DEPLOYMENT
uint256 constant public CHAIN_ID = 1; // @TODO SET FOR DEPLOYMENT
} }

View File

@ -31,6 +31,11 @@ interface IStructs {
NSignatureTypes // 0x05, number of signature types. Always leave at end. NSignatureTypes // 0x05, number of signature types. Always leave at end.
} }
struct StakingPoolApproval {
bytes32 poolId;
address makerAddress;
}
struct Timelock { struct Timelock {
uint64 lockedAt; uint64 lockedAt;
uint96 total; uint96 total;

View File

@ -16,15 +16,17 @@ pragma solidity ^0.5.5;
interface IWallet /* is EIP-1271 */ { interface IWallet /* is EIP-1271 */ {
/// @dev Verifies that a signature is valid. /// @dev Should return whether the signature provided is valid for the provided data
/// @param hash Message hash that is signed. /// @param data Arbitrary length data signed on the behalf of address(this)
/// @param signature Proof of signing. /// @param signature Signature byte array associated with _data
/// @return The function selector for this function ///
/// MUST return the bytes4 magic value 0x20c13b0b when function passes.
/// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
/// MUST allow external calls
function isValidSignature( function isValidSignature(
bytes32 hash, bytes calldata data,
bytes calldata signature bytes calldata signature)
)
external external
view view
returns (bytes32 isValidSignatureSelector); returns (bytes4 magicValue);
} }

View File

@ -0,0 +1,100 @@
/*
Copyright 2018 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.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibEIP712.sol";
import "../interfaces/IStructs.sol";
library LibEIP712Hash
{
// EIP712 Domain Name value for the Staking contract
string constant internal EIP712_STAKING_DOMAIN_NAME = "0x Protocol Staking";
// EIP712 Domain Version value for the Staking contract
string constant internal EIP712_STAKING_DOMAIN_VERSION = "1.0.0";
// Hash for the EIP712 StakingPool approval message
// keccak256(abi.encodePacked(
// "StakingPoolApproval(",
// "bytes32 poolId,",
// "address makerAddress",
// ")"
// ));
bytes32 constant internal EIP712_STAKING_POOL_APPROVAL_SCHEMA_HASH = 0x9b699f12ef1c0f7b43076182dcccc0c548c9a784cfcf27114f98d684e06826b6;
/// @dev Calculated the EIP712 hash of the StakingPool approval mesasage using the domain separator of this contract.
/// @param approval StakingPool approval message containing the transaction hash, transaction signature, and expiration of the approval.
/// @return EIP712 hash of the StakingPool approval message with the domain separator of this contract.
function _hashStakingPoolApprovalMessage(
IStructs.StakingPoolApproval memory approval,
address verifierAddress,
uint256 chainId
)
internal
pure
returns (bytes32 approvalHash)
{
approvalHash = _hashEIP712StakingMessage(
_hashStakingPoolApproval(approval),
verifierAddress,
chainId
);
return approvalHash;
}
/// @dev Calculates EIP712 encoding for a hash struct in the EIP712 domain
/// of this contract.
/// @param hashStruct The EIP712 hash struct.
/// @return EIP712 hash applied to this EIP712 Domain.
function _hashEIP712StakingMessage(
bytes32 hashStruct,
address verifierAddress,
uint256 chainId
)
internal
pure
returns (bytes32 result)
{
bytes32 eip712StakingDomainHash = LibEIP712._hashEIP712Domain(
EIP712_STAKING_DOMAIN_NAME,
EIP712_STAKING_DOMAIN_VERSION,
chainId,
verifierAddress
);
return LibEIP712._hashEIP712Message(eip712StakingDomainHash, hashStruct);
}
/// @dev Calculated the EIP712 hash of the StakingPool approval mesasage with no domain separator.
/// @param approval StakingPool approval message containing the transaction hash, transaction signature, and expiration of the approval.
/// @return EIP712 hash of the StakingPool approval message with no domain separator.
function _hashStakingPoolApproval(IStructs.StakingPoolApproval memory approval)
internal
pure
returns (bytes32 result)
{
result = keccak256(abi.encodePacked(
EIP712_STAKING_POOL_APPROVAL_SCHEMA_HASH,
approval.poolId,
approval.makerAddress
));
return result;
}
}

View File

@ -89,4 +89,20 @@ contract MixinPoolsWrapper is
makerAddresses = _getMakerAddressesForPool(makerId); makerAddresses = _getMakerAddressesForPool(makerId);
return makerAddresses; return makerAddresses;
} }
function isValidMakerSignature(bytes32 poolId, address makerAddress, bytes calldata makerSignature)
external
view
returns (bool isValid)
{
return _isValidMakerSignature(poolId, makerAddress, makerSignature);
}
function getStakingPoolApprovalMessageHash(bytes32 poolId, address makerAddress)
external
view
returns (bytes32 approvalHash)
{
return _getStakingPoolApprovalMessageHash(poolId, makerAddress);
}
} }

View File

@ -14,6 +14,7 @@
pragma solidity ^0.5.5; pragma solidity ^0.5.5;
import "../core/MixinSignatureValidator.sol"; import "../core/MixinSignatureValidator.sol";
import "../libs/LibEIP712Hash.sol";
contract MixinSignatureValidatorWrapper is contract MixinSignatureValidatorWrapper is
@ -28,9 +29,9 @@ contract MixinSignatureValidatorWrapper is
function isValidSignature( function isValidSignature(
bytes32 hash, bytes32 hash,
address signerAddress, address signerAddress,
bytes memory signature bytes calldata signature
) )
internal external
view view
returns (bool isValid) returns (bool isValid)
{ {

View File

@ -0,0 +1,40 @@
/*
Copyright 2018 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.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol";
import "../src/MixinSignatureValidator.sol";
import "../src/MixinTransactions.sol";
import "../src/MixinExchangeRichErrors.sol";
contract TestSignatureValidator is
LibEIP712ExchangeDomain,
MixinSignatureValidator,
MixinTransactions,
MixinExchangeRichErrors
{
// solhint-disable no-empty-blocks
constructor (uint256 chainId)
public
LibEIP712ExchangeDomain(chainId, address(0))
{}
}

View File

@ -57,6 +57,7 @@
"@0x/tslint-config": "^3.0.1", "@0x/tslint-config": "^3.0.1",
"@types/lodash": "4.14.104", "@types/lodash": "4.14.104",
"@types/node": "*", "@types/node": "*",
"@0x/utils": "^4.3.1",
"chai": "^4.0.1", "chai": "^4.0.1",
"chai-as-promised": "^7.1.0", "chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0", "chai-bignumber": "^3.0.0",
@ -71,12 +72,14 @@
"typescript": "3.0.1" "typescript": "3.0.1"
}, },
"dependencies": { "dependencies": {
"@0x/base-contract": "^5.0.5", "@0x/base-contract": "^5.1.0",
"@0x/contracts-utils": "^3.1.1", "@0x/contracts-test-utils": "^3.1.6",
"@0x/contracts-utils": "3.1.1",
"@0x/order-utils": "^7.2.0",
"@0x/types": "^2.2.2", "@0x/types": "^2.2.2",
"@0x/typescript-typings": "^4.2.2", "@0x/typescript-typings": "^4.2.2",
"@0x/utils": "^4.3.1", "@0x/utils": "^4.3.1",
"@0x/web3-wrapper": "^6.0.5", "@0x/web3-wrapper": "^6.0.6",
"ethereum-types": "^2.1.2", "ethereum-types": "^2.1.2",
"ethereumjs-util": "^5.1.1", "ethereumjs-util": "^5.1.1",
"lodash": "^4.17.11" "lodash": "^4.17.11"

View File

@ -29,6 +29,7 @@ describe('Staking Core', () => {
// constants // constants
const ZRX_TOKEN_DECIMALS = new BigNumber(18); const ZRX_TOKEN_DECIMALS = new BigNumber(18);
// tokens & addresses // tokens & addresses
let accounts: string[];
let owner: string; let owner: string;
let exchange: string; let exchange: string;
let stakers: string[]; let stakers: string[];
@ -48,7 +49,7 @@ describe('Staking Core', () => {
}); });
before(async () => { before(async () => {
// create accounts // create accounts
const accounts = await web3Wrapper.getAvailableAddressesAsync(); accounts = await web3Wrapper.getAvailableAddressesAsync();
owner = accounts[0]; owner = accounts[0];
exchange = accounts[1]; exchange = accounts[1];
stakers = accounts.slice(2, 5); stakers = accounts.slice(2, 5);
@ -774,7 +775,7 @@ describe('Staking Core', () => {
expect(ownerRewardFloatingPoint).to.be.bignumber.equal(expectedOwnerReward); expect(ownerRewardFloatingPoint).to.be.bignumber.equal(expectedOwnerReward);
}); });
it('pool management', async() => { it.only('pool management', async() => {
// create first pool // create first pool
const operatorAddress = stakers[0]; const operatorAddress = stakers[0];
const operatorShare = 39; const operatorShare = 39;
@ -786,8 +787,15 @@ describe('Staking Core', () => {
expect(nextPoolId).to.be.equal(expectedNextPoolId); expect(nextPoolId).to.be.equal(expectedNextPoolId);
// add maker to pool // add maker to pool
const makerAddress = makers[0]; const makerAddress = makers[0];
const makerSignature = "0x"; const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
await stakingWrapper.addMakerToPoolAsync(poolId, makerAddress, "0x00", makerSignature, operatorAddress); const makerApproval = stakingWrapper.getSignedApprovalForStakingPool(poolId, makerAddress, makerPrivateKey);
const onchainHash = await stakingWrapper.getStakingPoolApprovalMessageHashAsync(poolId, makerAddress);
console.log(`** ON-CHAIN HASH **\n`, onchainHash);
console.log(JSON.stringify(makerApproval, null , 4));
await stakingWrapper.addMakerToPoolAsync(poolId, makerAddress, "0x00", makerApproval.signature, operatorAddress);
throw new Error(`ADDED SUCCESSFULLY!`);
// check the pool id of the maker // check the pool id of the maker
const poolIdOfMaker = await stakingWrapper.getMakerPoolId(makerAddress); const poolIdOfMaker = await stakingWrapper.getMakerPoolId(makerAddress);
expect(poolIdOfMaker).to.be.equal(poolId); expect(poolIdOfMaker).to.be.equal(poolId);
@ -796,15 +804,16 @@ describe('Staking Core', () => {
expect(makerAddressesForPool).to.be.deep.equal([makerAddress]); expect(makerAddressesForPool).to.be.deep.equal([makerAddress]);
// try to add the same maker address again // try to add the same maker address again
await expectTransactionFailedAsync( await expectTransactionFailedAsync(
stakingWrapper.addMakerToPoolAsync(poolId, makerAddress, "0x00", makerSignature, operatorAddress), stakingWrapper.addMakerToPoolAsync(poolId, makerAddress, "0x00", makerApproval.signature, operatorAddress),
RevertReason.MakerAddressAlreadyRegistered RevertReason.MakerAddressAlreadyRegistered
); );
// try to add a new maker address from an address other than the pool operator // try to add a new maker address from an address other than the pool operator
const notOperatorAddress = owner; const notOperatorAddress = owner;
const anotherMakerAddress = makers[1]; const anotherMakerAddress = makers[1];
const anotherMakerSignature = "0x"; const anotherMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(anotherMakerAddress)];
const anotherMakerApproval = stakingWrapper.getSignedApprovalForStakingPool(poolId, anotherMakerAddress, anotherMakerPrivateKey);
await expectTransactionFailedAsync( await expectTransactionFailedAsync(
stakingWrapper.addMakerToPoolAsync(poolId, anotherMakerAddress, "0x00", anotherMakerSignature, notOperatorAddress), stakingWrapper.addMakerToPoolAsync(poolId, anotherMakerAddress, "0x00", anotherMakerApproval.signature, notOperatorAddress),
RevertReason.OnlyCallableByPoolOperator RevertReason.OnlyCallableByPoolOperator
); );
// try to remove the maker address from an address other than the operator // try to remove the maker address from an address other than the operator

View File

@ -0,0 +1,42 @@
import { signingUtils } from '@0x/contracts-test-utils';
import { SignatureType } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as ethUtil from 'ethereumjs-util';
import { hashUtils } from './hash_utils';
import { SignedStakingPoolApproval } from './types';
export class ApprovalFactory {
private readonly _privateKey: Buffer;
private readonly _verifyingContractAddress: string;
private readonly _chainId: number;
constructor(privateKey: Buffer, verifyingContractAddress: string, chainId: number) {
this._privateKey = privateKey;
this._verifyingContractAddress = verifyingContractAddress;
this._chainId = chainId;
}
public newSignedApproval(
poolId: string,
makerAddress: string,
signatureType: SignatureType = SignatureType.EthSign,
): SignedStakingPoolApproval {
const approvalHashBuff = hashUtils.getStakingPoolApprovalHashBuffer(
poolId,
makerAddress,
this._verifyingContractAddress,
this._chainId
);
console.log('*** APPROVAL HASH ***\n', approvalHashBuff);
const signatureBuff = signingUtils.signMessage(approvalHashBuff, this._privateKey, signatureType);
const signedApproval = {
makerAddress,
poolId,
verifyingContractAddress: this._verifyingContractAddress,
chainId: this._chainId,
signature: ethUtil.addHexPrefix(signatureBuff.toString('hex')),
};
return signedApproval;
}
}

View File

@ -10,4 +10,5 @@ export const constants = {
INITIAL_TIMELOCK_PERIOD: new BigNumber(0), INITIAL_TIMELOCK_PERIOD: new BigNumber(0),
EPOCH_PERIOD_IN_SECONDS: new BigNumber(1000), // @TODO SET FOR DEPLOYMENT*/ EPOCH_PERIOD_IN_SECONDS: new BigNumber(1000), // @TODO SET FOR DEPLOYMENT*/
TIMELOCK_PERIOD_IN_EPOCHS: new BigNumber(3), // @TODO SET FOR DEPLOYMENT TIMELOCK_PERIOD_IN_EPOCHS: new BigNumber(3), // @TODO SET FOR DEPLOYMENT
CHAIN_ID: 1,
}; };

View File

@ -0,0 +1,32 @@
import { eip712Utils } from '@0x/order-utils';
import { signTypedDataUtils } from '@0x/utils';
import * as _ from 'lodash';
export const hashUtils = {
getStakingPoolApprovalHashBuffer(
poolId: string,
makerAddress: string,
verifyingContractAddress: string,
chainId: number
): Buffer {
const typedData = eip712Utils.createStakingPoolApprovalTypedData(
poolId,
makerAddress,
verifyingContractAddress,
chainId
);
const hashBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
return hashBuffer;
},
getStakingPoolApprovalHashHex(
poolId: string,
makerAddress: string,
verifyingContractAddress: string,
chainId: number
): string {
const hashHex = `0x${hashUtils
.getStakingPoolApprovalHashBuffer(poolId, makerAddress, verifyingContractAddress, chainId)
.toString('hex')}`;
return hashHex;
},
};

View File

@ -1,14 +1,18 @@
import { constants, LogDecoder, txDefaults } from '@0x/contracts-test-utils'; import { LogDecoder, txDefaults } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper'; import { Web3Wrapper } from '@0x/web3-wrapper';
import * as chai from 'chai'; import * as chai from 'chai';
import { assetDataUtils } from '@0x/order-utils'; import { assetDataUtils } from '@0x/order-utils';
import { SignatureType } from '@0x/types';
import { LogWithDecodedArgs, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { LogWithDecodedArgs, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20'; import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
import { ERC20ProxyContract } from '@0x/contracts-asset-proxy'; import { ERC20ProxyContract } from '@0x/contracts-asset-proxy';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { artifacts, StakingEEventArgs, StakingContract, StakingProxyContract, ZrxVaultContract, RewardVaultContract, LibMathTestContract } from '../../src'; import { artifacts, StakingEEventArgs, StakingContract, StakingProxyContract, ZrxVaultContract, RewardVaultContract, LibMathTestContract } from '../../src';
import { ApprovalFactory } from './ApprovalFactory';
import { SignedStakingPoolApproval } from './types';
import { constants } from './constants';
const expect = chai.expect; const expect = chai.expect;
@ -272,6 +276,41 @@ export class StakingWrapper {
const makerAddresses = this.getStakingContract().getMakerAddressesForPool.getABIDecodedReturnData(returndata); const makerAddresses = this.getStakingContract().getMakerAddressesForPool.getABIDecodedReturnData(returndata);
return makerAddresses; return makerAddresses;
} }
public async isValidMakerSignatureAsync(poolId: string, makerAddress: string, makerSignature: string): Promise<Boolean> {
const calldata = this.getStakingContract().isValidMakerSignature.getABIEncodedTransactionData(poolId, makerAddress, makerSignature);
const returndata = await this._callAsync(calldata);
const isValid = this.getStakingContract().isValidMakerSignature.getABIDecodedReturnData(returndata);
return isValid;
}
public async getStakingPoolApprovalMessageHashAsync(poolId: string, makerAddress: string): Promise<string> {
const calldata = this.getStakingContract().getStakingPoolApprovalMessageHash.getABIEncodedTransactionData(poolId, makerAddress);
const returndata = await this._callAsync(calldata);
const messageHash = this.getStakingContract().getStakingPoolApprovalMessageHash.getABIDecodedReturnData(returndata);
return messageHash;
}
public getSignedApprovalForStakingPool(poolId: string, makerAddress: string, makerPrivateKey: Buffer, signatureType: SignatureType = SignatureType.EthSign): SignedStakingPoolApproval {
const signedStakingPoolApproval = this.getSignedApprovalForStakingPoolFlexible(
poolId,
makerAddress,
makerPrivateKey,
this.getStakingProxyContract().address,
constants.CHAIN_ID,
signatureType
);
return signedStakingPoolApproval;
}
public getSignedApprovalForStakingPoolFlexible(
poolId: string,
makerAddress: string,
makerPrivateKey: Buffer,
verifierAddress: string,
chainId: number,
signatureType: SignatureType = SignatureType.EthSign,
): SignedStakingPoolApproval {
const approvalFactory = new ApprovalFactory(makerPrivateKey, verifierAddress, chainId);
const signedStakingPoolApproval = approvalFactory.newSignedApproval(poolId, makerAddress, signatureType);
return signedStakingPoolApproval;
}
///// EPOCHS ///// ///// EPOCHS /////
public async goToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> { public async goToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().finalizeFees.getABIEncodedTransactionData(); const calldata = this.getStakingContract().finalizeFees.getABIEncodedTransactionData();

View File

@ -0,0 +1,12 @@
import { BigNumber } from '@0x/utils';
export interface StakingPoolApproval {
makerAddress: string,
poolId: string,
verifyingContractAddress: string,
chainId: number
}
export interface SignedStakingPoolApproval extends StakingPoolApproval {
signature: string;
}

View File

@ -149,6 +149,15 @@ export const constants = {
{ name: 'approvalExpirationTimeSeconds', type: 'uint256' }, { name: 'approvalExpirationTimeSeconds', type: 'uint256' },
], ],
}, },
STAKING_DOMAIN_NAME: '0x Protocol Staking',
STAKING_DOMAIN_VERSION: '1.0.0',
STAKING_POOL_APPROVAL_SCHEMA: {
name: 'StakingPoolApproval',
parameters: [
{ name: 'poolId', type: 'bytes32' },
{ name: 'makerAddress', type: 'address' },
],
},
ERC20_METHOD_ABI, ERC20_METHOD_ABI,
ERC721_METHOD_ABI, ERC721_METHOD_ABI,
MULTI_ASSET_METHOD_ABI, MULTI_ASSET_METHOD_ABI,

View File

@ -125,4 +125,34 @@ export const eip712Utils = {
); );
return typedData; return typedData;
}, },
/**
* Creates an Coordiantor typedData EIP712TypedData object for use with the Coordinator extension contract
* @return A typed data object
*/
createStakingPoolApprovalTypedData: (
poolId: string,
makerAddress: string,
verifyingContractAddress: string,
chainId: number
): EIP712TypedData => {
const domain = {
name: constants.STAKING_DOMAIN_NAME,
version: constants.STAKING_DOMAIN_VERSION,
verifyingContractAddress,
chainId
};
const approval = {
poolId,
makerAddress,
};
const typedData = eip712Utils.createTypedData(
constants.STAKING_POOL_APPROVAL_SCHEMA.name,
{
StakingPoolApproval: constants.STAKING_POOL_APPROVAL_SCHEMA.parameters,
},
approval,
domain,
);
return typedData;
},
}; };

View File

@ -69,6 +69,8 @@ export const signTypedDataUtils = {
let deps = signTypedDataUtils._findDependencies(primaryType, types); let deps = signTypedDataUtils._findDependencies(primaryType, types);
deps = deps.filter(d => d !== primaryType); deps = deps.filter(d => d !== primaryType);
deps = [primaryType].concat(deps.sort()); deps = [primaryType].concat(deps.sort());
console.log('*** DEPS ****\n', JSON.stringify(deps, null, 4));
console.log('*** TYPES ****\n', JSON.stringify(types, null, 4));
let result = ''; let result = '';
for (const dep of deps) { for (const dep of deps) {
result += `${dep}(${types[dep].map(({ name, type }) => `${type} ${name}`).join(',')})`; result += `${dep}(${types[dep].map(({ name, type }) => `${type} ${name}`).join(',')})`;

View File

@ -649,6 +649,13 @@
dependencies: dependencies:
"@0x/base-contract" "^4.0.3" "@0x/base-contract" "^4.0.3"
"@0x/abi-gen-wrappers@^4.2.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@0x/abi-gen-wrappers/-/abi-gen-wrappers-4.3.0.tgz#b36616b129f44072474a7d5739299e4b1068e3d4"
integrity sha512-KnpoHbbqfidECl4hTGOvBaCj1N3LASHcdNO38yOMo1eO4B0H+kuyDVFjN3nbNt7lnXTPJ+DdaQpoDVQwySdEDQ==
dependencies:
"@0x/base-contract" "^5.1.0"
"@0x/abi-gen@^2.0.9": "@0x/abi-gen@^2.0.9":
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/@0x/abi-gen/-/abi-gen-2.1.1.tgz#2ca9072e64a2a46b6149aaea434f09d5dbf0866f" resolved "https://registry.yarnpkg.com/@0x/abi-gen/-/abi-gen-2.1.1.tgz#2ca9072e64a2a46b6149aaea434f09d5dbf0866f"
@ -697,13 +704,13 @@
ethers "~4.0.4" ethers "~4.0.4"
lodash "^4.17.11" lodash "^4.17.11"
"@0x/contract-addresses@^2.2.1": "@0x/contract-addresses@^2.2.1", "@0x/contract-addresses@^2.3.1":
version "2.3.3" version "2.3.3"
resolved "https://registry.yarnpkg.com/@0x/contract-addresses/-/contract-addresses-2.3.3.tgz#8cf009e7668c2fccca416177c85f3f6612c724c6" resolved "https://registry.yarnpkg.com/@0x/contract-addresses/-/contract-addresses-2.3.3.tgz#8cf009e7668c2fccca416177c85f3f6612c724c6"
dependencies: dependencies:
lodash "^4.17.11" lodash "^4.17.11"
"@0x/contract-artifacts@^1.3.0": "@0x/contract-artifacts@^1.3.0", "@0x/contract-artifacts@^1.5.0":
version "1.5.1" version "1.5.1"
resolved "https://registry.npmjs.org/@0x/contract-artifacts/-/contract-artifacts-1.5.1.tgz#6fba56a1d3e2d5d897a75fcfa432e49e2ebb17a7" resolved "https://registry.npmjs.org/@0x/contract-artifacts/-/contract-artifacts-1.5.1.tgz#6fba56a1d3e2d5d897a75fcfa432e49e2ebb17a7"
@ -746,6 +753,21 @@
ethereumjs-util "^5.1.1" ethereumjs-util "^5.1.1"
lodash "^4.17.5" lodash "^4.17.5"
"@0x/contracts-utils@3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@0x/contracts-utils/-/contracts-utils-3.1.1.tgz#d3d70ec4f5f9e10d85abc7d0c1b2b7bb34f38927"
dependencies:
"@0x/base-contract" "^5.0.5"
"@0x/order-utils" "^7.2.0"
"@0x/types" "^2.2.2"
"@0x/typescript-typings" "^4.2.2"
"@0x/utils" "^4.3.1"
"@0x/web3-wrapper" "^6.0.5"
bn.js "^4.11.8"
ethereum-types "^2.1.2"
ethereumjs-util "^5.1.1"
lodash "^4.17.11"
"@0x/coordinator-server@^0.1.3": "@0x/coordinator-server@^0.1.3":
version "0.1.3" version "0.1.3"
resolved "https://registry.yarnpkg.com/@0x/coordinator-server/-/coordinator-server-0.1.3.tgz#5fbb7c11bb641aa5386797769cab9a68a7d15b79" resolved "https://registry.yarnpkg.com/@0x/coordinator-server/-/coordinator-server-0.1.3.tgz#5fbb7c11bb641aa5386797769cab9a68a7d15b79"
@ -797,6 +819,29 @@
ethers "~4.0.4" ethers "~4.0.4"
lodash "^4.17.11" lodash "^4.17.11"
"@0x/order-utils@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@0x/order-utils/-/order-utils-7.2.0.tgz#c73d81e3225e9ec7736f9789e14388c9fe2b831c"
integrity sha512-P1tVRKyUvfU/yjYS+bbK+InZr0V2wPlX7nTp6ORTuPWd5zjWrRN7rpq50qvgY+UZCeajcQh9g4Dt1l+LWot/AA==
dependencies:
"@0x/abi-gen-wrappers" "^4.2.0"
"@0x/assert" "^2.0.9"
"@0x/base-contract" "^5.0.5"
"@0x/contract-addresses" "^2.3.1"
"@0x/contract-artifacts" "^1.5.0"
"@0x/json-schemas" "^3.0.9"
"@0x/types" "^2.2.2"
"@0x/typescript-typings" "^4.2.2"
"@0x/utils" "^4.3.1"
"@0x/web3-wrapper" "^6.0.5"
"@types/node" "*"
bn.js "^4.11.8"
ethereum-types "^2.1.2"
ethereumjs-abi "0.6.5"
ethereumjs-util "^5.1.1"
ethers "~4.0.4"
lodash "^4.17.11"
"@0x/subproviders@^4.1.1": "@0x/subproviders@^4.1.1":
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-4.1.2.tgz#ab7bb0f482b11ccb4615fb5dd8ca85199cd0ae23" resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-4.1.2.tgz#ab7bb0f482b11ccb4615fb5dd8ca85199cd0ae23"