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

[cli] transfer:erc20 and balance commands #7753

Merged
merged 5 commits into from
Apr 26, 2021
Merged
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
24 changes: 19 additions & 5 deletions packages/cli/src/commands/account/balance.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
import { BaseCommand } from '../../base'
import { printValueMap } from '../../utils/cli'
import { Args } from '../../utils/command'
import { failWith, printValueMap } from '../../utils/cli'
import { Args, Flags } from '../../utils/command'

export default class Balance extends BaseCommand {
static description = 'View Celo Dollar and Gold balances for an address'
static description = 'View Celo Stables and CELO balances for an address'

static flags = {
...BaseCommand.flags,
erc20Address: Flags.address({
description: 'Address of generic ERC-20 token to also check balance for',
}),
}

static args = [Args.address('address')]

static examples = ['balance 0x5409ed021d9299bf6814279a6a1411a7e866a631']
static examples = [
'balance 0x5409ed021d9299bf6814279a6a1411a7e866a631',
'balance 0x5409ed021d9299bf6814279a6a1411a7e866a631 --erc20Address 0x765DE816845861e75A25fCA122bb6898B8B1282a',
]

async run() {
const { args } = this.parse(Balance)
const { args, flags } = this.parse(Balance)

console.log('All balances expressed in units of 10^-18.')
printValueMap(await this.kit.getTotalBalance(args.address))
if (flags.erc20Address) {
try {
const erc20 = await this.kit.contracts.getErc20(flags.erc20Address)
printValueMap({ erc20: await erc20.balanceOf(args.address) })
} catch {
failWith('Invalid erc20 address')
}
}
}
}
2 changes: 1 addition & 1 deletion packages/cli/src/commands/exchange/show.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StableTokenInfo } from '@celo/contractkit/src/celo-tokens'
import { StableTokenInfo } from '@celo/contractkit/lib/celo-tokens'
import { flags } from '@oclif/command'
import { cli } from 'cli-ux'
import { BaseCommand } from '../../base'
Expand Down
57 changes: 57 additions & 0 deletions packages/cli/src/commands/transfer/erc20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Ierc20 } from '@celo/contractkit/lib/generated/IERC20'
import { Erc20Wrapper } from '@celo/contractkit/lib/wrappers/Erc20Wrapper'
import { flags } from '@oclif/command'
import BigNumber from 'bignumber.js'
import { BaseCommand } from '../../base'
import { newCheckBuilder } from '../../utils/checks'
import { displaySendTx, failWith } from '../../utils/cli'
import { Flags } from '../../utils/command'

export default class TransferErc20 extends BaseCommand {
static description = 'Transfer ERC20 to a specified address'

static flags = {
...BaseCommand.flags,
erc20Address: Flags.address({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can define this flag once and reuse it across the commands

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know if we require this in more that these two commands. We could leave it this way and if we see another pattern, we could extract it in the future

required: true,
description: "Custom erc20 to check it's balance too",
}),
from: Flags.address({
required: true,
description: 'Address of the sender',
}),
to: Flags.address({
required: true,
description: 'Address of the receiver',
}),
value: flags.string({
required: true,
description: 'Amount to transfer (in wei)',
}),
}

static examples = [
'erc20 --erc20Address 0x765DE816845861e75A25fCA122bb6898B8B1282a --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value 10000000000000000000',
]

async run() {
const res = this.parse(TransferErc20)

const from: string = res.flags.from
const to: string = res.flags.to
const value = new BigNumber(res.flags.value)

this.kit.defaultAccount = from
let celoToken: Erc20Wrapper<Ierc20>
try {
celoToken = await this.kit.contracts.getErc20(res.flags.erc20Address)
// this call allow us to check if it is a valid erc20
await celoToken.balanceOf(res.flags.from)
} catch {
failWith('Invalid erc20 address')
}
await newCheckBuilder(this).hasEnoughErc20(from, value, res.flags.erc20Address).runChecks()

await displaySendTx('transfer', celoToken.transfer(to, value.toFixed()))
}
}
10 changes: 10 additions & 0 deletions packages/cli/src/utils/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,16 @@ class CheckBuilder {
)
}

hasEnoughErc20 = (account: Address, value: BigNumber, erc20: Address) => {
const valueInEth = this.kit.connection.web3.utils.fromWei(value.toFixed(), 'ether')
return this.addCheck(`Account has at least ${valueInEth} erc20 token`, () =>
this.kit.contracts
.getErc20(erc20)
.then((goldToken) => goldToken.balanceOf(account))
.then((balance) => balance.gte(value))
)
}

exceedsProposalMinDeposit = (deposit: BigNumber) =>
this.addCheck(
`Deposit is greater than or equal to governance proposal minDeposit`,
Expand Down
16 changes: 12 additions & 4 deletions packages/docs/command-line-interface/account.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions packages/docs/command-line-interface/transfer.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/sdk/contractkit/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum CeloContract {
DowntimeSlasher = 'DowntimeSlasher',
Election = 'Election',
EpochRewards = 'EpochRewards',
ERC20 = 'ERC20',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like the wrong place for this to live

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is "ERC20" a CeloContract?

Copy link
Contributor Author

@gastonponti gastonponti Apr 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should define what we think it is a CeloContract. In a way, we think of those as the contracts supported/maintained by celo. The ERC20 is a standard accepted by everyone.
Let's say that the contract does not exist in the ethereum network, we would probably want to handle and support that contract.
So, in a way, it is something standard for celo too, we are just taking the advantage of reuse it from other network.

If we think of the CeloContracts as only the ones that live on the registry, we should also remove the multisig from that list, for example.

If we think of the CeloContracts, as the one accepted by the community, all the ercXXX accepted, should be supported too

Escrow = 'Escrow',
Exchange = 'Exchange',
ExchangeEUR = 'ExchangeEUR',
Expand Down
7 changes: 7 additions & 0 deletions packages/sdk/contractkit/src/contract-cache.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { CeloContract } from './base'
import { StableToken } from './celo-tokens'
import { Ierc20 } from './generated/IERC20'
import { ContractKit } from './kit'
import { AccountsWrapper } from './wrappers/Accounts'
import { AttestationsWrapper } from './wrappers/Attestations'
import { BlockchainParametersWrapper } from './wrappers/BlockchainParameters'
import { DoubleSigningSlasherWrapper } from './wrappers/DoubleSigningSlasher'
import { DowntimeSlasherWrapper } from './wrappers/DowntimeSlasher'
import { ElectionWrapper } from './wrappers/Election'
import { Erc20Wrapper } from './wrappers/Erc20Wrapper'
// import { EpochRewardsWrapper } from './wrappers/EpochRewards'
import { EscrowWrapper } from './wrappers/Escrow'
import { ExchangeWrapper } from './wrappers/Exchange'
Expand All @@ -31,6 +33,7 @@ const WrapperFactories = {
[CeloContract.DowntimeSlasher]: DowntimeSlasherWrapper,
[CeloContract.Election]: ElectionWrapper,
// [CeloContract.EpochRewards]?: EpochRewardsWrapper,
[CeloContract.ERC20]: Erc20Wrapper,
[CeloContract.Escrow]: EscrowWrapper,
[CeloContract.Exchange]: ExchangeWrapper,
[CeloContract.ExchangeEUR]: ExchangeWrapper,
Expand Down Expand Up @@ -63,6 +66,7 @@ interface WrapperCacheMap {
[CeloContract.DowntimeSlasher]?: DowntimeSlasherWrapper
[CeloContract.Election]?: ElectionWrapper
// [CeloContract.EpochRewards]?: EpochRewardsWrapper
[CeloContract.ERC20]?: Erc20Wrapper<Ierc20>
[CeloContract.Escrow]?: EscrowWrapper
[CeloContract.Exchange]?: ExchangeWrapper
[CeloContract.ExchangeEUR]?: ExchangeWrapper
Expand Down Expand Up @@ -115,6 +119,9 @@ export class WrapperCache {
// getEpochRewards() {
// return this.getContract(CeloContract.EpochRewards)
// }
getErc20(address: string) {
return this.getContract(CeloContract.ERC20, address)
}
getEscrow() {
return this.getContract(CeloContract.Escrow)
}
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/contractkit/src/web3-contract-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { newFreezer } from './generated/Freezer'
import { newGasPriceMinimum } from './generated/GasPriceMinimum'
import { newGoldToken } from './generated/GoldToken'
import { newGovernance } from './generated/Governance'
import { newIerc20 } from './generated/IERC20'
import { newLockedGold } from './generated/LockedGold'
import { newMetaTransactionWallet } from './generated/MetaTransactionWallet'
import { newMetaTransactionWalletDeployer } from './generated/MetaTransactionWalletDeployer'
Expand All @@ -40,6 +41,7 @@ export const ContractFactories = {
[CeloContract.DowntimeSlasher]: newDowntimeSlasher,
[CeloContract.Election]: newElection,
[CeloContract.EpochRewards]: newEpochRewards,
[CeloContract.ERC20]: newIerc20,
[CeloContract.Escrow]: newEscrow,
[CeloContract.Exchange]: newExchange,
[CeloContract.ExchangeEUR]: newExchangeEur,
Expand Down Expand Up @@ -98,6 +100,9 @@ export class Web3ContractCache {
getEpochRewards() {
return this.getContract(CeloContract.EpochRewards)
}
getErc20(address: string) {
return this.getContract(CeloContract.ERC20, address)
}
getEscrow() {
return this.getContract(CeloContract.Escrow)
}
Expand Down