Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Add support for wallets without getOwner() interface (#3779)
Browse files Browse the repository at this point in the history
* Make Wallet Mist compatible #3282

* Owners icons on load

* Fix oversized logo on load

* Don't fetch registry twice (even when pending)

* Better logging...

* Better contract view : show if no events // show loading events

* Better decimal typed input

* PR grumble
  • Loading branch information
ngotchac authored and jacogr committed Dec 10, 2016
1 parent 923f85d commit 6655e7e
Show file tree
Hide file tree
Showing 15 changed files with 226 additions and 72 deletions.
40 changes: 24 additions & 16 deletions js/src/contracts/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import * as abis from './abi';
export default class Registry {
constructor (api) {
this._api = api;
this._contracts = [];

this._contracts = {};
this._pendingContracts = {};

this._instance = null;
this._fetching = false;
this._queue = [];
Expand Down Expand Up @@ -59,20 +62,25 @@ export default class Registry {
getContract (_name) {
const name = _name.toLowerCase();

return new Promise((resolve, reject) => {
if (this._contracts[name]) {
resolve(this._contracts[name]);
return;
}

this
.lookupAddress(name)
.then((address) => {
this._contracts[name] = this._api.newContract(abis[name], address);
resolve(this._contracts[name]);
})
.catch(reject);
});
if (this._contracts[name]) {
return Promise.resolve(this._contracts[name]);
}

if (this._pendingContracts[name]) {
return this._pendingContracts[name];
}

const promise = this
.lookupAddress(name)
.then((address) => {
this._contracts[name] = this._api.newContract(abis[name], address);
delete this._pendingContracts[name];
return this._contracts[name];
});

this._pendingContracts[name] = promise;

return promise;
}

getContractInstance (_name) {
Expand All @@ -89,7 +97,7 @@ export default class Registry {
return instance.getAddress.call({}, [sha3, 'A']);
})
.then((address) => {
console.log('lookupAddress', name, sha3, address);
console.log('[lookupAddress]', `(${sha3}) ${name}: ${address}`);
return address;
});
}
Expand Down
3 changes: 2 additions & 1 deletion js/src/modals/CreateWallet/createWalletStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { wallet as walletAbi } from '~/contracts/abi';
import { wallet as walletCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';

import { validateUint, validateAddress, validateName } from '~/util/validation';
import { toWei } from '~/api/util/wei';
import WalletsUtils from '~/util/wallets';

const STEPS = {
Expand All @@ -47,7 +48,7 @@ export default class CreateWalletStore {
address: '',
owners: [],
required: 1,
daylimit: 0,
daylimit: toWei(1),

name: '',
description: ''
Expand Down
3 changes: 0 additions & 3 deletions js/src/redux/providers/personal.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ export default class Personal {
}

this._store.dispatch(personalAccountsInfo(accountsInfo));
})
.then((subscriptionId) => {
console.log('personal._subscribeAccountsInfo', 'subscriptionId', subscriptionId);
});
}

Expand Down
3 changes: 0 additions & 3 deletions js/src/redux/providers/signer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ export default class Signer {
}

this._store.dispatch(signerRequestsToConfirm(pending || []));
})
.then((subscriptionId) => {
console.log('signer._subscribeRequestsToConfirm', 'subscriptionId', subscriptionId);
});
}
}
3 changes: 0 additions & 3 deletions js/src/redux/providers/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ export default class Status {
.catch((error) => {
console.warn('status._subscribeBlockNumber', 'getBlockByNumber', error);
});
})
.then((subscriptionId) => {
console.log('status._subscribeBlockNumber', 'subscriptionId', subscriptionId);
});
}

Expand Down
32 changes: 19 additions & 13 deletions js/src/ui/Form/Input/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,32 +113,38 @@ export default class Input extends Component {
<TextField
autoComplete='off'
className={ className }
style={ textFieldStyle }

readOnly={ readOnly }

errorText={ error }

floatingLabelFixed
floatingLabelText={ label }
fullWidth

hintText={ hint }
id={ NAME_ID }
inputStyle={ inputStyle }
fullWidth

max={ max }
min={ min }

multiLine={ multiLine }
name={ NAME_ID }
id={ NAME_ID }

onBlur={ this.onBlur }
onChange={ this.onChange }
onKeyDown={ this.onKeyDown }
onPaste={ this.onPaste }

readOnly={ readOnly }
rows={ rows }
style={ textFieldStyle }
type={ type || 'text' }

underlineDisabledStyle={ UNDERLINE_DISABLED }
underlineStyle={ readOnly ? UNDERLINE_READONLY : UNDERLINE_NORMAL }
underlineFocusStyle={ readOnly ? { display: 'none' } : null }
underlineShow={ !hideUnderline }

value={ value }
onBlur={ this.onBlur }
onChange={ this.onChange }
onKeyDown={ this.onKeyDown }
onPaste={ this.onPaste }
inputStyle={ inputStyle }
min={ min }
max={ max }
>
{ children }
</TextField>
Expand Down
52 changes: 43 additions & 9 deletions js/src/ui/Form/TypedInput/typedInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ export default class TypedInput extends Component {
};

state = {
isEth: true,
isEth: false,
ethValue: 0
};

componentDidMount () {
componentWillMount () {
if (this.props.isEth && this.props.value) {
this.setState({ ethValue: fromWei(this.props.value) });
this.setState({ isEth: true, ethValue: fromWei(this.props.value) });
}
}

Expand Down Expand Up @@ -164,28 +164,32 @@ export default class TypedInput extends Component {
}

if (type === ABI_TYPES.INT) {
return this.renderNumber();
return this.renderEth();
}

if (type === ABI_TYPES.FIXED) {
return this.renderNumber();
return this.renderFloat();
}

return this.renderDefault();
}

renderEth () {
const { ethValue } = this.state;
const { ethValue, isEth } = this.state;

const value = ethValue && typeof ethValue.toNumber === 'function'
? ethValue.toNumber()
: ethValue;

const input = isEth
? this.renderFloat(value, this.onEthValueChange)
: this.renderInteger(value, this.onEthValueChange);

return (
<div className={ styles.ethInput }>
<div className={ styles.input }>
{ this.renderNumber(value, this.onEthValueChange) }
{ this.state.isEth ? (<div className={ styles.label }>ETH</div>) : null }
{ input }
{ isEth ? (<div className={ styles.label }>ETH</div>) : null }
</div>
<div className={ styles.toggle }>
<Toggle
Expand All @@ -198,8 +202,9 @@ export default class TypedInput extends Component {
);
}

renderNumber (value = this.props.value, onChange = this.onChange) {
renderInteger (value = this.props.value, onChange = this.onChange) {
const { label, error, param, hint, min, max } = this.props;

const realValue = value && typeof value.toNumber === 'function'
? value.toNumber()
: value;
Expand All @@ -212,6 +217,35 @@ export default class TypedInput extends Component {
error={ error }
onChange={ onChange }
type='number'
step={ 1 }
min={ min !== null ? min : (param.signed ? null : 0) }
max={ max !== null ? max : null }
/>
);
}

/**
* Decimal numbers have to be input via text field
* because of some react issues with input number fields.
* Once the issue is fixed, this could be a number again.
*
* @see https://github.com/facebook/react/issues/1549
*/
renderFloat (value = this.props.value, onChange = this.onChange) {
const { label, error, param, hint, min, max } = this.props;

const realValue = value && typeof value.toNumber === 'function'
? value.toNumber()
: value;

return (
<Input
label={ label }
hint={ hint }
value={ realValue }
error={ error }
onChange={ onChange }
type='text'
min={ min !== null ? min : (param.signed ? null : 0) }
max={ max !== null ? max : null }
/>
Expand Down
6 changes: 3 additions & 3 deletions js/src/ui/Page/page.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

.layout {
padding: 0.25em 0.25em 1em 0.25em;
}

.layout>div {
padding-bottom: 0.75em;
> * {
margin-bottom: 0.75em;
}
}
77 changes: 75 additions & 2 deletions js/src/util/wallets.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

import { range } from 'lodash';
import { range, uniq } from 'lodash';

import { bytesToHex, toHex } from '~/api/util/format';
import { validateAddress } from '~/util/validation';

export default class WalletsUtils {

Expand All @@ -26,10 +27,82 @@ export default class WalletsUtils {

static fetchOwners (walletContract) {
const walletInstance = walletContract.instance;

return walletInstance
.m_numOwners.call()
.then((mNumOwners) => {
return Promise.all(range(mNumOwners.toNumber()).map((idx) => walletInstance.getOwner.call({}, [ idx ])));
const promises = range(mNumOwners.toNumber())
.map((idx) => walletInstance.getOwner.call({}, [ idx ]));

return Promise
.all(promises)
.then((owners) => {
const uniqOwners = uniq(owners);

// If all owners are the zero account : must be Mist wallet contract
if (uniqOwners.length === 1 && /^(0x)?0*$/.test(owners[0])) {
return WalletsUtils.fetchMistOwners(walletContract, mNumOwners.toNumber());
}

return owners;
});
});
}

static fetchMistOwners (walletContract, mNumOwners) {
const walletAddress = walletContract.address;

return WalletsUtils
.getMistOwnersOffset(walletContract)
.then((result) => {
if (!result || result.offset === -1) {
return [];
}

const owners = [ result.address ];

if (mNumOwners === 1) {
return owners;
}

const initOffset = result.offset + 1;
let promise = Promise.resolve();

range(initOffset, initOffset + mNumOwners - 1).forEach((offset) => {
promise = promise
.then(() => {
return walletContract.api.eth.getStorageAt(walletAddress, offset);
})
.then((result) => {
const resultAddress = '0x' + (result || '').slice(-40);
const { address } = validateAddress(resultAddress);

owners.push(address);
});
});

return promise.then(() => owners);
});
}

static getMistOwnersOffset (walletContract, offset = 3) {
return walletContract.api.eth
.getStorageAt(walletContract.address, offset)
.then((result) => {
if (result && !/^(0x)?0*$/.test(result)) {
const resultAddress = '0x' + result.slice(-40);
const { address, addressError } = validateAddress(resultAddress);

if (!addressError) {
return { offset, address };
}
}

if (offset >= 100) {
return { offset: -1 };
}

return WalletsUtils.getMistOwnersOffset(walletContract, offset + 1);
});
}

Expand Down
11 changes: 9 additions & 2 deletions js/src/views/Accounts/Summary/summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ export default class Summary extends Component {
return true;
}

const prevOwners = this.props.owners;
const nextOwners = nextProps.owners;

if (!isEqual(prevOwners, nextOwners)) {
return true;
}

return false;
}

Expand Down Expand Up @@ -123,8 +130,8 @@ export default class Summary extends Component {
return (
<div className={ styles.owners }>
{
ownersValid.map((owner) => (
<div key={ owner.address }>
ownersValid.map((owner, index) => (
<div key={ `${index}_${owner.address}` }>
<div
data-tip
data-for={ `owner_${owner.address}` }
Expand Down
Loading

0 comments on commit 6655e7e

Please sign in to comment.