Skip to content

Commit

Permalink
feat: show fallback arbitration options only to specific accounts in …
Browse files Browse the repository at this point in the history
…the multisig (#699)

* Adding gnosis contract

* fix: mainnet fallback multisig address

* determine if the users belongs to the arbitrator multisig

* feat: show fallback arbitration options only to specific accounts in the multisig

* fix: code review
  • Loading branch information
richard-ramos authored Feb 12, 2020
1 parent ee70bc2 commit fa6bc69
Show file tree
Hide file tree
Showing 12 changed files with 990 additions and 21 deletions.
883 changes: 883 additions & 0 deletions contracts/misc/GnosisSafe.sol

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions embarkConfig/contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const GAS_PRICE = "5000000000"; //5 gwei


const dataMigration = require('./data.js');
const GNOSIS_ABI = [{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}];

module.exports = {
default: {
Expand Down Expand Up @@ -122,6 +123,8 @@ module.exports = {
KyberNetworkProxy: {},
KyberFeeBurner: { // TODO: replace BURN_ADDRESS with "$StakingPool"
args: ["$SNT", BURN_ADDRESS, "$KyberNetworkProxy", "0x0000000000000000000000000000000000000000", "300"]
},
GnosisSafe: {
}
}
},
Expand Down Expand Up @@ -158,6 +161,10 @@ module.exports = {
},
RelayHub: {
address: '0xd216153c06e857cd7f72665e0af1d7d82172f494'
},
GnosisSafe: {
address: FALLBACK_ARBITRATOR_RINKEBY,
abiDefinition: GNOSIS_ABI
}
}
},
Expand Down Expand Up @@ -256,6 +263,10 @@ module.exports = {
},
KyberFeeBurner: {
address: "0x7702CaaE3D8feE750c4464d80FCb14Ce05e00743"
},
GnosisSafe: {
address: FALLBACK_ARBITRATOR_MAINNET,
abiDefinition: GNOSIS_ABI
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions src/js/components/UserInformation/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import Address from './Address';
import Identicon from "./Identicon";
import {withTranslation} from "react-i18next";

const UserInformation = ({t, identiconSeed, username, reputation, isArbitrator, nbReleasedTrades, nbCreatedTrades}) => (
const UserInformation = ({t, identiconSeed, isFallbackArbitrator, username, reputation, isArbitrator, nbReleasedTrades, nbCreatedTrades}) => (
<Row className="m-0 text-center mb-4">
<Col xs="12">
<Identicon seed={identiconSeed} className="rounded-circle border" scale={8} />
{isArbitrator && <span className="icon-badge">{t('general.arbitrator')}</span>}
{isArbitrator && !isFallbackArbitrator && <span className="icon-badge">{t('general.arbitrator')}</span>}
{isFallbackArbitrator && <span className="icon-badge">{t('profile.fallbackArbitrator')}</span>}

</Col>
<Col xs="12">
<h4 className="font-weight-bold">{username}</h4>
Expand All @@ -32,7 +34,8 @@ UserInformation.propTypes = {
reputation: PropTypes.object,
isArbitrator: PropTypes.bool,
nbReleasedTrades: PropTypes.number,
nbCreatedTrades: PropTypes.number
nbCreatedTrades: PropTypes.number,
isFallbackArbitrator: PropTypes.bool
};

export default withTranslation()(UserInformation);
5 changes: 4 additions & 1 deletion src/js/features/arbitration/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
LOAD_ARBITRATOR_SCORES,
UNBLACKLIST_SELLER,
GET_BLACKLISTED_SELLERS,
GET_FALLBACK_ARBITRATOR
GET_FALLBACK_ARBITRATOR,
IS_FALLBACK_ARBITRATOR
} from './constants';
import Escrow from '../../../embarkArtifacts/contracts/Escrow';
import ArbitrationLicense from '../../../embarkArtifacts/contracts/ArbitrationLicense';
Expand Down Expand Up @@ -87,3 +88,5 @@ export const unBlacklistSeller = (sellerAddress) => ({type: UNBLACKLIST_SELLER,
export const loadArbitratorScores = () => ({type: LOAD_ARBITRATOR_SCORES});

export const getFallbackArbitrator = () => ({type: GET_FALLBACK_ARBITRATOR});

export const checkIfFallbackArbitrator = () => ({type: IS_FALLBACK_ARBITRATOR});
4 changes: 4 additions & 0 deletions src/js/features/arbitration/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ export const GET_FALLBACK_ARBITRATOR = 'GET_FALLBACK_ARBITRATOR';
export const GET_FALLBACK_ARBITRATOR_FAILED = 'GET_FALLBACK_ARBITRATOR_FAILED';
export const GET_FALLBACK_ARBITRATOR_SUCCEEDED = 'GET_FALLBACK_ARBITRATOR_SUCCEEDED';

export const IS_FALLBACK_ARBITRATOR = 'IS_FALLBACK_ARBITRATOR';
export const IS_FALLBACK_ARBITRATOR_FAILED = 'IS_FALLBACK_ARBITRATOR_FAILED';
export const IS_FALLBACK_ARBITRATOR_SUCCEEDED = 'IS_FALLBACK_ARBITRATOR_SUCCEEDED';

// motives:
export const UNRESPONSIVE = '1';
export const PAYMENT = '2';
Expand Down
11 changes: 9 additions & 2 deletions src/js/features/arbitration/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {
ACCEPTED,
GET_BLACKLISTED_SELLERS_SUCCEEDED, GET_BLACKLISTED_SELLERS_FAILED,
BLACKLIST_SELLER_SUCCEEDED, UNBLACKLIST_SELLER_SUCCEEDED,
RESET_ARBITRATOR_SCORES, ADD_ARBITRATOR_SCORE, GET_FALLBACK_ARBITRATOR_FAILED, GET_FALLBACK_ARBITRATOR_SUCCEEDED
RESET_ARBITRATOR_SCORES, ADD_ARBITRATOR_SCORE, GET_FALLBACK_ARBITRATOR_FAILED, GET_FALLBACK_ARBITRATOR_SUCCEEDED, IS_FALLBACK_ARBITRATOR_FAILED, IS_FALLBACK_ARBITRATOR_SUCCEEDED
} from './constants';
import { fromTokenDecimals } from '../../utils/numbers';
import {RESET_STATE, PURGE_STATE} from "../network/constants";
Expand All @@ -72,7 +72,8 @@ const DEFAULT_STATE = {
arbitratorRequests: [],
blacklistedSellers: [],
arbitratorScores: {},
fallbackArbitrator: ''
fallbackArbitrator: '',
isFallbackArbitrator: false
};

function isActionNeeded(escrows) {
Expand Down Expand Up @@ -152,6 +153,7 @@ function reducer(state = DEFAULT_STATE, action) {
case ACCEPT_ARBITRATOR_REQUEST_FAILED:
case REJECT_ARBITRATOR_REQUEST_FAILED:
case GET_BLACKLISTED_SELLERS_FAILED:
case IS_FALLBACK_ARBITRATOR_FAILED:
return {
...state, ...{
errorGet: action.error,
Expand Down Expand Up @@ -200,6 +202,11 @@ function reducer(state = DEFAULT_STATE, action) {
...state,
fallbackArbitrator: action.fallbackArbitrator
};
case IS_FALLBACK_ARBITRATOR_SUCCEEDED:
return {
...state,
isFallbackArbitrator: action.isFallbackArbitrator
};
case GET_ARBITRATORS_SUCCEEDED:
return {
...state,
Expand Down
30 changes: 28 additions & 2 deletions src/js/features/arbitration/saga.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import Escrow from '../../../embarkArtifacts/contracts/Escrow';
import ArbitrationLicense from '../../../embarkArtifacts/contracts/ArbitrationLicense';
import SNT from '../../../embarkArtifacts/contracts/SNT';
import GnosisSafe from '../../../embarkArtifacts/contracts/GnosisSafe';
import OfferStore from '../../../embarkArtifacts/contracts/OfferStore';
import moment from 'moment';
import {promiseEventEmitter, doTransaction} from '../../utils/saga';
Expand All @@ -26,7 +27,8 @@ import {
BLACKLIST_SELLER, BLACKLIST_SELLER_PRE_SUCCESS, BLACKLIST_SELLER_FAILED, BLACKLIST_SELLER_SUCCEEDED,
UNBLACKLIST_SELLER, UNBLACKLIST_SELLER_FAILED, UNBLACKLIST_SELLER_PRE_SUCCESS, UNBLACKLIST_SELLER_SUCCEEDED,
GET_FALLBACK_ARBITRATOR, GET_FALLBACK_ARBITRATOR_FAILED, GET_FALLBACK_ARBITRATOR_SUCCEEDED,
GET_BLACKLISTED_SELLERS, GET_BLACKLISTED_SELLERS_FAILED, GET_BLACKLISTED_SELLERS_SUCCEEDED, LOAD_ARBITRATOR_SCORES, RESET_ARBITRATOR_SCORES, ADD_ARBITRATOR_SCORE
GET_BLACKLISTED_SELLERS, GET_BLACKLISTED_SELLERS_FAILED, GET_BLACKLISTED_SELLERS_SUCCEEDED, LOAD_ARBITRATOR_SCORES, RESET_ARBITRATOR_SCORES, ADD_ARBITRATOR_SCORE,
IS_FALLBACK_ARBITRATOR, IS_FALLBACK_ARBITRATOR_SUCCEEDED, IS_FALLBACK_ARBITRATOR_FAILED
} from './constants';
import ArbitrationLicenseProxy from '../../../embarkArtifacts/contracts/ArbitrationLicenseProxy';
import EscrowProxy from '../../../embarkArtifacts/contracts/EscrowProxy';
Expand Down Expand Up @@ -113,6 +115,7 @@ export function *doGetEscrows({includeFallbackDisputes, isArbitrator}) {
addressCompare(escrow.arbitrator, web3.eth.defaultAccount) ||
(addressCompare(escrow.buyer, web3.eth.defaultAccount) || addressCompare(escrow.seller, web3.eth.defaultAccount))
){
escrow.isFallback = includeFallbackDisputes && escrow.arbitration.arbitratorTimeout < (Date.now()/1000);
escrows.push(escrow);
}
}
Expand Down Expand Up @@ -211,6 +214,28 @@ export function *doGetArbitratorBlacklist() {
}
}

export function *onIsFallbackArbitrator() {
yield takeEvery(IS_FALLBACK_ARBITRATOR, doCheckFallbackArbitrator);
}


export function *doCheckFallbackArbitrator() {
try {
const fallbackArbitrator = yield call(Escrow.methods.fallbackArbitrator().call);
const code = yield call(web3.eth.getCode, fallbackArbitrator);
let isFallbackArbitrator = false;
if(code === '0x'){
isFallbackArbitrator = addressCompare(fallbackArbitrator, web3.eth.defaultAccount);
} else {
isFallbackArbitrator = yield call(GnosisSafe.methods.isOwner(web3.eth.defaultAccount).call);
}
yield put({type: IS_FALLBACK_ARBITRATOR_SUCCEEDED, isFallbackArbitrator});
} catch (error) {
console.error(error);
yield put({type: IS_FALLBACK_ARBITRATOR_FAILED, error: error.message});
}
}

export function *onGetFallbackArbitrator() {
yield takeEvery(GET_FALLBACK_ARBITRATOR, doGetFallbackArbitrator);
}
Expand Down Expand Up @@ -348,5 +373,6 @@ export default [
fork(onUnBlacklistSeller),
fork(onRejectRequest),
fork(onLoadArbitratorScores),
fork(onGetFallbackArbitrator)
fork(onGetFallbackArbitrator),
fork(onIsFallbackArbitrator)
];
1 change: 1 addition & 0 deletions src/js/features/arbitration/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const txHash = state => state.arbitration.txHash;
export const arbitrators = state => state.arbitration.arbitrators;
export const arbitratorScore = state => arbitrator => state.arbitration.arbitratorScores[toChecksumAddress(arbitrator)];
export const fallbackArbitrator = state => state.arbitration.fallbackArbitrator;
export const isFallbackArbitrator = state => state.arbitration.isFallbackArbitrator;

export const getArbitration = (state) => {
const arbitration = state.arbitration.arbitration;
Expand Down
5 changes: 4 additions & 1 deletion src/js/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@
"disputesToResolve": "{{nbDisputes}} disputes to resolve",
"arbitratorSettings": "Arbitrator settings",
"pendingRequests": "{{nbRequests}} pending requests",
"profileSettings": "Profile settings"
"profileSettings": "Profile settings",
"fallbackDisputes": "Fallback disputes",
"fallbackArbitrator": "Fallback Arbitrator"
},
"sellerApproval": {
"title": "Seller Management",
Expand Down Expand Up @@ -448,6 +450,7 @@
"stake2": "for arbitrator license",
"ownDispute": "You cannot arbitrate your own disputes",
"notYours": "You are not the arbitrator of this dispute",
"notAuthorized": "You are not authorized to see this page",
"yourDecision": "Your decision",
"goesTo": "{{tokenAmount}} {{tokenSymbol}} goes to:",
"resolve": "Resolve dispute",
Expand Down
12 changes: 9 additions & 3 deletions src/js/pages/Arbitration/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class Arbitration extends Component {
}

render() {
const {t, escrow, address, loading, isFallbackDispute, buyerInfo, sellerInfo, isStatus} = this.props;
const {t, escrow, address, loading, isFallbackDispute, buyerInfo, sellerInfo, isStatus, isFallbackArbitrator} = this.props;

if (!escrow || !buyerInfo || !sellerInfo) {
return <Loading/>;
Expand All @@ -217,6 +217,10 @@ class Arbitration extends Component {
return <ErrorInformation message={t('arbitration.notYours')}/>;
}

if(isFallbackDispute && !isFallbackArbitrator){
return <ErrorInformation message={t('arbitration.notAuthorized')}/>;
}

if (loading) {
return <Loading mining={true}/>;
}
Expand Down Expand Up @@ -297,7 +301,8 @@ Arbitration.propTypes = {
resolveDispute: PropTypes.func,
loading: PropTypes.bool,
isStatus: PropTypes.bool,
isFallbackDispute: PropTypes.bool
isFallbackDispute: PropTypes.bool,
isFallbackArbitrator: PropTypes.bool
};


Expand All @@ -311,7 +316,8 @@ const mapStateToProps = (state, props) => {
sellerInfo: escrow ? metadata.selectors.getProfile(state, escrow.seller) : null,
buyerInfo: escrow ? metadata.selectors.getProfile(state, escrow.buyer) : null,
loading: arbitration.selectors.isLoading(state),
isFallbackDispute: !!props.match.params.isFallbackDispute
isFallbackDispute: !!props.match.params.isFallbackDispute,
isFallbackArbitrator: arbitration.selectors.isFallbackArbitrator(state)
};
};

Expand Down
8 changes: 5 additions & 3 deletions src/js/pages/MyDisputes/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ class MyDisputes extends Component {


render() {
const {profile, address, includeFallbackDisputes, fallbackArbitrator, network} = this.props;
const {profile, address, includeFallbackDisputes, fallbackArbitrator, network, isFallbackArbitrator} = this.props;
if (!profile) return <Loading page={true}/>;

if (!profile.isArbitrator && !includeFallbackDisputes) {
if (!profile.isArbitrator && !isFallbackArbitrator) {
return <NoLicense arbitratorPage/>;
}

Expand All @@ -67,6 +67,7 @@ MyDisputes.propTypes = {
includeFallbackDisputes: PropTypes.bool,
getFallbackArbitrator: PropTypes.func,
fallbackArbitrator: PropTypes.string,
isFallbackArbitrator: PropTypes.bool,
network: PropTypes.object
};

Expand All @@ -79,7 +80,8 @@ const mapStateToProps = (state, props) => {
includeFallbackDisputes: !!props.match.params.includeFallbackDisputes,
disputes: arbitration.selectors.escrows(state),
fallbackArbitrator: arbitration.selectors.fallbackArbitrator(state),
network: network.selectors.getNetwork(state)
network: network.selectors.getNetwork(state),
isFallbackArbitrator: arbitration.selectors.isFallbackArbitrator(state)
};
};

Expand Down
32 changes: 26 additions & 6 deletions src/js/pages/MyProfile/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class MyProfile extends Component {
this.load();
}

if(!oldProps.isFallbackArbitrator && this.props.isFallbackArbitrator) {
this.props.getDisputedEscrows(true);
}

if(this.props.profile && this.props.profile.isArbitrator && !this.props.profile.contactData){
return this.props.history.push("/profile/settings/contact");
}
Expand All @@ -65,9 +69,10 @@ class MyProfile extends Component {

load() {
this.props.loadProfile(this.props.address);
this.props.getDisputedEscrows();
this.props.getDisputedEscrows(this.props.isFallbackArbitrator);
this.props.getArbitratorRequests();
this.props.resetNotificationWarnings();
this.props.checkIfFallbackArbitrator();
}

watchEscrows() {
Expand All @@ -83,7 +88,7 @@ class MyProfile extends Component {
}

render() {
const {t, profile, address, requests, trades, enableEthereum} = this.props;
const {t, profile, address, requests, trades, enableEthereum, isFallbackArbitrator} = this.props;

if (!this.props.isEip1102Enabled || !this.props.address) {
return <ConnectWallet enableEthereum={enableEthereum} />;
Expand All @@ -94,11 +99,12 @@ class MyProfile extends Component {
const activeOffers = profile.offers.filter(x => !x.deleted && !addressCompare(x.arbitrator, zeroAddress)).length;
const pendingRequests = requests.reduce((a, b) => a + (b.status === arbitration.constants.AWAIT ? 1 : 0), 0);
const openDisputes = this.props.disputes.filter(x => x.arbitration.open && !addressCompare(x.seller, address) && !addressCompare(x.buyer, address) && addressCompare(x.arbitrator, address));
const openFallbackDisputes = this.props.disputes.filter(x => x.isFallback && x.arbitration.open);
const activeTrades = trades.filter(x => !escrow.helpers.completedStates.includes(x.status)).length;

return (
<Fragment>
<UserInformation isArbitrator={profile.isArbitrator} reputation={profile.reputation}
<UserInformation isArbitrator={profile.isArbitrator} isFallbackArbitrator={isFallbackArbitrator} reputation={profile.reputation}
identiconSeed={profile.address} username={profile.username}/>
<ProfileButton linkTo="/profile/trades" image={iconTrades} title={t('profile.myTrades')}
subtitle={t('profile.nbActive', {nb: activeTrades})} active={this.props.tradeActionNeeded}/>
Expand Down Expand Up @@ -126,6 +132,16 @@ class MyProfile extends Component {
</Fragment>
)}

{isFallbackArbitrator && (
<Fragment>
<p className="text-muted mt-4">{t('profile.fallbackArbitrator')}</p>
<ProfileButton linkTo="/profile/disputes/fallback" image={iconDispute} title={t('profile.fallbackDisputes')}
subtitle={t('profile.disputesToResolve', {nbDisputes: openFallbackDisputes.length})}
active={true}/>
<Separator/>
</Fragment>
)}

<ProfileButton linkTo="/profile/settings" image={iconSettings} title={t('profile.profileSettings')}/>
</Fragment>
);
Expand All @@ -150,7 +166,9 @@ MyProfile.propTypes = {
requests: PropTypes.array,
enableEthereum: PropTypes.func,
getArbitratorRequests: PropTypes.func,
resetNotificationWarnings: PropTypes.func
resetNotificationWarnings: PropTypes.func,
checkIfFallbackArbitrator: PropTypes.func,
isFallbackArbitrator: PropTypes.bool
};

const mapStateToProps = state => {
Expand All @@ -166,14 +184,16 @@ const mapStateToProps = state => {
requests: arbitration.selectors.getArbitratorRequests(state),
isEip1102Enabled: metadata.selectors.isEip1102Enabled(state),
tradeActionNeeded: escrow.selectors.actionNeeded(state),
arbitrationActionNeeded: arbitration.selectors.actionNeeded(state)
arbitrationActionNeeded: arbitration.selectors.actionNeeded(state),
isFallbackArbitrator: arbitration.selectors.isFallbackArbitrator(state)
};
};

export default connect(
mapStateToProps,
{
loadProfile: metadata.actions.load,
checkIfFallbackArbitrator: arbitration.actions.checkIfFallbackArbitrator,
getDisputedEscrows: arbitration.actions.getDisputedEscrows,
watchEscrow: escrow.actions.watchEscrow,
getArbitratorRequests: arbitration.actions.getArbitratorRequests,
Expand Down

0 comments on commit fa6bc69

Please sign in to comment.