diff --git a/CHANGELOG.md b/CHANGELOG.md index 26398124a..15c2d3327 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- [#436](https://github.com/poanetwork/nifty-wallet/pull/436) - Allow master copy pattern in importing of proxy contract - [#435](https://github.com/poanetwork/nifty-wallet/pull/435) - Allow array input type in contracts interactions - [#434](https://github.com/poanetwork/nifty-wallet/pull/434) - Add support of tuple type at interaction with read-only contract methods - [#432](https://github.com/poanetwork/nifty-wallet/pull/432) - bump rsk-contract-metadata dependency diff --git a/old-ui/app/accounts/import/contract.js b/old-ui/app/accounts/import/contract.js index 65368c635..3d7df0f8a 100644 --- a/old-ui/app/accounts/import/contract.js +++ b/old-ui/app/accounts/import/contract.js @@ -25,6 +25,8 @@ class ContractImportView extends Component { static propTypes = { error: PropTypes.string, network: PropTypes.string, + RPC_URL: PropTypes.string, + provider: PropTypes.object, type: PropTypes.string, displayWarning: PropTypes.func, importNewAccount: PropTypes.func, @@ -118,12 +120,12 @@ class ContractImportView extends Component { autodetectContractABI = () => { const { contractAddr, web3 } = this.state - const { type, network } = this.props + const { type, network, provider, RPC_URL } = this.props if (!contractAddr || !web3.isAddress(contractAddr)) { this.clearABI() return } - getFullABI(web3.eth, contractAddr, network, type) + getFullABI(web3.eth, contractAddr, network, type, RPC_URL, provider) .then(finalABI => { if (finalABI) { finalABI = JSON.stringify(finalABI) @@ -218,6 +220,8 @@ function mapStateToProps (state) { const result = { error: warning && (warning || warning.message), network: state.metamask.network, + provider: state.metamask.provider, + RPC_URL: state.appState.RPC_URL, } return result diff --git a/old-ui/app/accounts/import/helpers.js b/old-ui/app/accounts/import/helpers.js index ea6e50b6f..eabda0171 100644 --- a/old-ui/app/accounts/import/helpers.js +++ b/old-ui/app/accounts/import/helpers.js @@ -1,5 +1,8 @@ import log from 'loglevel' import { importTypes } from './enums' +import Web3 from 'web3' +import ethNetProps from 'eth-net-props' +import abi from 'web3-eth-abi' const nestedJsonObjToArray = (jsonObj) => { return jsonObjToArray(jsonObj) @@ -100,44 +103,88 @@ const fetchABI = (addr, network) => { }) } -const getFullABI = (eth, contractAddr, network, type) => { +const getFullABI = (eth, contractAddr, network, type, RPC_URL, provider) => { return new Promise((resolve, reject) => { fetchABI(contractAddr, network) .then((targetABI) => { targetABI = targetABI && JSON.parse(targetABI) - let finalABI = targetABI if (type === importTypes.CONTRACT.PROXY) { - if (!eth.contract(targetABI).at(contractAddr).implementation) { + if (!eth.contract(targetABI).at(contractAddr).implementation && !isMasterCopyPattern(targetABI)) { const e = { message: 'This is not a valid Delegate Proxy contract', } reject(e) } try { - eth.contract(targetABI).at(contractAddr).implementation.call((err, implAddr) => { - if (err) { - reject(err) - } else { - fetchABI(implAddr, network) - .then((implABI) => { - implABI = implABI && JSON.parse(implABI) - finalABI = implABI ? targetABI.concat(implABI) : targetABI - resolve(finalABI) - }) - .catch(e => reject(e)) + if (isMasterCopyPattern(targetABI)) { + let rpcUrl = RPC_URL || provider.rpcTarget + if (rpcUrl === '') { + rpcUrl = ethNetProps.RPCEndpoints(network)[0] } - }) + getImplAddrFromMasterCopyPattern(contractAddr, rpcUrl) + .then(implAddr => { + fetchImplementationAndCombine(implAddr, targetABI, network, resolve, reject) + }) + .catch(err => { + reject(err) + }) + } else { + eth.contract(targetABI).at(contractAddr).implementation.call((err, implAddr) => { + if (err) { + reject(err) + } else { + fetchImplementationAndCombine(implAddr, targetABI, network, resolve, reject) + } + }) + } } catch (e) { reject(e) } } else { - resolve(finalABI) + resolve(targetABI) } }) .catch(e => { reject(e) }) }) } +const isMasterCopyPattern = (abi) => { + return abi.some(method => { + return isMasterCopyInput(method.inputs) + }) +} + +const isMasterCopyInput = (inputs) => { + return inputs && inputs.find(input => { + return input.name === '_masterCopy' + }) +} + +const getImplAddrFromMasterCopyPattern = (address, rpcUrl) => { + return new Promise((resolve, reject) => { + const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl)) + web3.eth.getStorageAt(address, 0, 'latest', (err, result) => { + if (err) { + reject(err) + } + if (result) { + const implAddr = abi.decodeParameter('address', result) + resolve(implAddr) + } + }) + }) +} + +const fetchImplementationAndCombine = (implAddr, proxyABI, network, resolve, reject) => { + return fetchABI(implAddr, network) + .then((implABI) => { + implABI = implABI && JSON.parse(implABI) + const finalABI = implABI ? proxyABI.concat(implABI) : proxyABI + resolve(finalABI) + }) + .catch(e => reject(e)) +} + module.exports = { nestedJsonObjToArray, getFullABI, diff --git a/old-ui/app/components/account-dropdowns/account-dropdowns.component.js b/old-ui/app/components/account-dropdowns/account-dropdowns.component.js index b513ff938..f37bceb40 100644 --- a/old-ui/app/components/account-dropdowns/account-dropdowns.component.js +++ b/old-ui/app/components/account-dropdowns/account-dropdowns.component.js @@ -43,6 +43,8 @@ class AccountDropdowns extends Component { style: PropTypes.object, enableAccountOptions: PropTypes.bool, enableAccountsSelector: PropTypes.bool, + RPC_URL: PropTypes.string, + provider: PropTypes.object, } constructor (props) { @@ -229,9 +231,9 @@ class AccountDropdowns extends Component { } updateABI = async () => { - const { actions, selected, network } = this.props + const { actions, selected, network, RPC_URL, provider } = this.props actions.showLoadingIndication() - getFullABI(this.web3.eth, selected, network, importTypes.CONTRACT.PROXY) + getFullABI(this.web3.eth, selected, network, importTypes.CONTRACT.PROXY, RPC_URL, provider) .then(finalABI => { actions.updateABI(selected, network, finalABI) .then() @@ -331,6 +333,15 @@ class AccountDropdowns extends Component { } } +function mapStateToProps (state) { + const result = { + provider: state.metamask.provider, + RPC_URL: state.appState.RPC_URL, + } + + return result +} + const mapDispatchToProps = (dispatch) => { return { actions: { @@ -349,5 +360,5 @@ const mapDispatchToProps = (dispatch) => { } module.exports = { - AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns), + AccountDropdowns: connect(mapStateToProps, mapDispatchToProps)(AccountDropdowns), } diff --git a/old-ui/app/components/send/send-contract.js b/old-ui/app/components/send/send-contract.js index d7ede129c..8a9b0f5c2 100644 --- a/old-ui/app/components/send/send-contract.js +++ b/old-ui/app/components/send/send-contract.js @@ -138,7 +138,7 @@ class SendTransactionScreen extends PersistentForm { copyDisabled: true, } - let rpcUrl = props.RPC_URL ? props.RPC_URL : props.provider.rpcTarget + let rpcUrl = props.RPC_URL || props.provider.rpcTarget if (rpcUrl === '') { rpcUrl = ethNetProps.RPCEndpoints(props.network)[0] }