Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token manager frontend (Adding move tokens in contract and in front end) #1136

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion apps/token-manager/app/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class App extends React.PureComponent {
)
return holder ? holder.balance : new BN('0')
}
handleUpdateTokens = ({ amount, holder, mode }) => {
handleUpdateTokens = ({ amount, holder, to, mode }) => {
const { api } = this.props

// Don't care about responses
Expand All @@ -47,6 +47,9 @@ class App extends React.PureComponent {
if (mode === 'remove') {
api.burn(holder, amount).toPromise()
}
if (mode === 'move') {
api.transfer_from_to(holder, to, amount).toPromise()
}

this.handleSidepanelClose()
}
Expand All @@ -59,6 +62,15 @@ class App extends React.PureComponent {
sidepanelOpened: true,
})
}
handleLaunchMoveTokensNoHolder = () => {
this.handleLaunchMoveTokens('')
}
handleLaunchMoveTokens = address => {
this.setState({
assignTokensConfig: {mode: 'move', holderAddress: address },
sidepanelOpened: true,
})
}
handleLaunchRemoveTokens = address => {
this.setState({
assignTokensConfig: { mode: 'remove', holderAddress: address },
Expand Down Expand Up @@ -113,6 +125,7 @@ class App extends React.PureComponent {
<React.Fragment>
<AppHeader
onAssignHolder={this.handleLaunchAssignTokensNoHolder}
onMoveFrom={this.handleLaunchMoveTokensNoHolder}
tokenSymbol={tokenSymbol}
/>
<Holders
Expand Down
11 changes: 11 additions & 0 deletions apps/token-manager/app/src/components/AppHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
const AppHeader = React.memo(function AppHeader({
tokenSymbol,
onAssignHolder,
onMoveFrom,
}) {
const theme = useTheme()
const { layoutName } = useLayout()
Expand Down Expand Up @@ -48,19 +49,29 @@ const AppHeader = React.memo(function AppHeader({
</div>
}
secondary={
<>
<Button
mode="strong"
onClick={onAssignHolder}
label="Add tokens"
icon={<IconPlus />}
display={layoutName === 'small' ? 'icon' : 'label'}
/>
<Button
mode="strong"
onClick={onMoveFrom}
label="Move tokens"
icon={<IconSwap />}
display={layoutName === 'small' ? 'icon' : 'label'}
/>
</>
}
/>
)
})
AppHeader.propTypes = {
onAssignHolder: PropTypes.func.isRequired,
onMoveFrom: PropTypes.func.isRequired,
tokenSymbol: PropTypes.string,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function UpdateTokenPanel({
}) {
return (
<SidePanel
title={mode === 'assign' ? 'Add tokens' : 'Remove tokens'}
title={mode === 'assign' ? 'Add tokens' : mode === 'move' ? 'Move tokens' : 'Remove tokens'}
opened={opened}
onClose={onClose}
onTransitionEnd={onTransitionEnd}
Expand Down Expand Up @@ -72,6 +72,12 @@ function usePanelForm({
warning: null,
})

const [receiverField, setReceiverField] = useState({
error: null,
value: finalHolder
warning: null,
})

const [amountField, setAmountField] = useState({
error: null,
max: '',
Expand Down Expand Up @@ -288,6 +294,7 @@ function TokenPanelContent({
onUpdateTokens({
amount: fieldsData.amount,
holder: fieldsData.holder,
to: fieldsData.to,
mode,
})
},
Expand All @@ -309,12 +316,16 @@ function TokenPanelContent({
>
{mode === 'assign'
? 'This action will create tokens and transfer them to the recipient below.'
: move === 'move'
? 'This action will move tokens from the first address to the second below.'
: 'This action will remove tokens from the account below.'}
</Info>
<Field
label={
mode === 'assign'
? 'Recipient (must be a valid Ethereum address)'
: mode === 'move'
? 'Holder (must be a valid Ethereum address)'
: 'Account (must be a valid Ethereum address)'
}
>
Expand All @@ -327,10 +338,24 @@ function TokenPanelContent({
/>
</Field>

<Field
label= 'Receiver (must be a valid Ethereum address)'
>
<LocalIdentitiesAutoComplete
ref={holderAddress ? undefined : holderInputRef}
value={holderField.value}
onChange={updateHolder}
wide
required
/>
</Field>

<Field
label={
mode === 'assign'
? 'Number of tokens to add'
: mode === 'move'
? 'Number of tokens to move'
: 'Number of tokens to remove'
}
>
Expand All @@ -348,7 +373,7 @@ function TokenPanelContent({
</Field>

<Button mode="strong" type="submit" disabled={submitDisabled} wide>
{mode === 'assign' ? 'Add tokens' : 'Remove tokens'}
{mode === 'assign' ? 'Add tokens' : mode === 'move' ? 'Move tokens' : 'Remove tokens'}
</Button>

<div
Expand Down
9 changes: 9 additions & 0 deletions apps/token-manager/arapp.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
"Token amount"
]
},
{
"name": "Move tokens",
"id": "TRANSFER_FROM_TO_ROLE",
"params": [
"Holder",
"Receiver",
"Token amount"
]
},
{
"name": "Revoke vesting",
"id": "REVOKE_VESTINGS_ROLE",
Expand Down
16 changes: 16 additions & 0 deletions apps/token-manager/contracts/TokenManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ contract TokenManager is ITokenController, IForwarder, AragonApp {
bytes32 public constant MINT_ROLE = keccak256("MINT_ROLE");
bytes32 public constant ISSUE_ROLE = keccak256("ISSUE_ROLE");
bytes32 public constant ASSIGN_ROLE = keccak256("ASSIGN_ROLE");
bytes32 public constant TRANSFER_FROM_TO_ROLE = keccak256("TRANSFER_FROM_TO_ROLE");
bytes32 public constant REVOKE_VESTINGS_ROLE = keccak256("REVOKE_VESTINGS_ROLE");
bytes32 public constant BURN_ROLE = keccak256("BURN_ROLE");

Expand Down Expand Up @@ -133,6 +134,16 @@ contract TokenManager is ITokenController, IForwarder, AragonApp {
token.destroyTokens(_holder, _amount);
}

/**
* @notice Transfer `@tokenAmount(self.token(): address, _amount, false)` tokens from `_holder` to `_receiver`
* @param _holder Holder of tokens being transferred
* @param _receiver The address receiving the tokens
* @param _amount Number of tokens transferred
*/
function transfer_from_to(address _holder, address _receiver, uint256 _amount) external authP(TRANSFER_FROM_TO_ROLE, arr(_holder, _receiver, _amount)) {
_transfer_from_to(_holder, _receiver, _amount);
}

/**
* @notice Assign `@tokenAmount(self.token(): address, _amount, false)` tokens to `_receiver` from the Token Manager's holdings with a `_revokable : 'revokable' : ''` vesting starting at `@formatDate(_start)`, cliff at `@formatDate(_cliff)` (first portion of tokens transferable), and completed vesting at `@formatDate(_vested)` (all tokens transferable)
* @param _receiver The address receiving the tokens, cannot be Token Manager itself
Expand Down Expand Up @@ -323,6 +334,11 @@ contract TokenManager is ITokenController, IForwarder, AragonApp {
token.generateTokens(_receiver, _amount); // minime.generateTokens() never returns false
}

function _transfer_from_to(address _holder, address _receiver, uint256 _amount) internal {
require(_isBalanceIncreaseAllowed(_holder, _receiver, _amount), ERROR_BALANCE_INCREASE_NOT_ALLOWED);
require(token.transferFrom(_holder, _receiver, _amount), ERROR_ASSIGN_TRANSFER_FROM_REVERTED);
}

function _isBalanceIncreaseAllowed(address _receiver, uint256 _inc) internal view returns (bool) {
// Max balance doesn't apply to the token manager itself
if (_receiver == address(this)) {
Expand Down