Skip to content

Commit

Permalink
refactor(AENS): Remove duplicated resolveName (#1022)
Browse files Browse the repository at this point in the history
* feat(AENS): Remove duplicated resolveName methods. Prepare one generic helper for name resolving

* feat(AE): Add name resolving to `AE.transferFunds` method

* feat(AE): Fix `resolveName` for old RPC Wallet<->AEPP communication

* feat(AE): Fix `resolveName` for Contract ACI
  • Loading branch information
nduchak authored Jun 9, 2020
1 parent a8b0aab commit 289598c
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 55 deletions.
21 changes: 3 additions & 18 deletions es/ae/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,8 @@ import ContractACI from '../contract/aci'
import BigNumber from 'bignumber.js'
import NodePool from '../node-pool'
import { AMOUNT, DEPOSIT, DRY_RUN_ACCOUNT, GAS, MIN_GAS_PRICE } from '../tx/builder/schema'
import { decode, isNameValid, produceNameId } from '../tx/builder/helpers'
import { decode, produceNameId } from '../tx/builder/helpers'
import TxObject from '../tx/tx-object'
import { assertedType } from '../utils/crypto'

async function resolveContractAddress (addressOrName) {
if (typeof addressOrName !== 'string') throw new Error('Invalid contract address. Should be a string with "ct" prefix or "AENS" name')
if (assertedType(addressOrName, 'ct', true)) return addressOrName
if (isNameValid(addressOrName)) {
const name = await this.getName(addressOrName).catch(_ => null)
if (!name) throw new Error('Name not found')
const contractPointer = name.pointers.find(({ id }) => id.split('_')[0] === 'ct')
if (!contractPointer) throw new Error(`Name ${addressOrName} do not have pointers for contract`)
return contractPointer.id
}
throw new Error('Invalid contract address. Should be a string with "ct" prefix or "AENS" name')
}

function sendAndProcess (tx, options) {
return async function (onSuccess, onError) {
Expand Down Expand Up @@ -186,7 +172,7 @@ async function contractCallStatic (source, address, name, args = [], { top, opti
// Prepare `call` transaction
const tx = await this.contractCallTx(R.merge(opt, {
callerId,
contractId: await this.resolveContractAddress(address),
contractId: await this.resolveName(address, 'ct', { resolveByNode: true }),
callData,
nonce
}))
Expand Down Expand Up @@ -242,7 +228,7 @@ async function contractCall (source, address, name, argsOrCallData = [], options

const tx = await this.contractCallTx(R.merge(opt, {
callerId: await this.address(opt),
contractId: await this.resolveContractAddress(address),
contractId: await this.resolveName(address, 'ct', { resolveByNode: true }),
callData: Array.isArray(argsOrCallData) ? await this.contractEncodeCall(source, name, argsOrCallData, opt) : argsOrCallData
}))

Expand Down Expand Up @@ -499,7 +485,6 @@ export const ContractAPI = Ae.compose(ContractBase, ContractACI, {
contractEncodeCall,
contractDecodeData,
dryRunContractTx,
resolveContractAddress,
handleCallError,
// Delegation for contract
// AENS
Expand Down
26 changes: 3 additions & 23 deletions es/ae/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ import Account from '../account'
import TxBuilder from '../tx/builder'
import * as R from 'ramda'
import { BigNumber } from 'bignumber.js'
import { isAddressValid } from '../utils/crypto'
import { isNameValid, produceNameId } from '../tx/builder/helpers'
import { AE_AMOUNT_FORMATS } from '../utils/amount-formatter'

/**
Expand Down Expand Up @@ -69,30 +67,11 @@ async function signUsingGA (tx, options = {}) {
*/
async function spend (amount, recipientId, options = {}) {
const opt = R.merge(this.Ae.defaults, options)
recipientId = await this.resolveRecipientName(recipientId, options)
recipientId = await this.resolveName(recipientId, 'ak', options)
const spendTx = await this.spendTx(R.merge(opt, { senderId: await this.address(opt), recipientId, amount }))
return this.send(spendTx, opt)
}

/**
* Resolve AENS name and return name hash
* @param {String} nameOrAddress
* @param {Boolean} verify
* @return {String} Address or AENS name hash
*/
async function resolveRecipientName (nameOrAddress, { verify = false }) {
if (isAddressValid(nameOrAddress)) return nameOrAddress
if (isNameValid(nameOrAddress)) {
// Validation
if (verify) {
const { pointers } = await this.getName(nameOrAddress)
if (!pointers.find(({ id }) => id.split('_')[0] === 'ak')) throw new Error(`Name ${nameOrAddress} do not have pointers for account`)
}
return produceNameId(nameOrAddress)
}
throw new Error('Invalid recipient name or address: ' + nameOrAddress)
}

/**
* Send a percentage of funds to another account
* @instance
Expand All @@ -106,6 +85,7 @@ async function resolveRecipientName (nameOrAddress, { verify = false }) {
async function transferFunds (percentage, recipientId, options = { excludeFee: false }) {
if (percentage < 0 || percentage > 1) throw new Error(`Percentage should be a number between 0 and 1, got ${percentage}`)
const opt = R.merge(this.Ae.defaults, options)
recipientId = await this.resolveName(recipientId, 'ak', opt)

const requestTransferAmount = BigNumber(await this.balance(await this.address())).times(percentage)
let spendTx = await this.spendTx(R.merge(opt, { senderId: await this.address(), recipientId, amount: requestTransferAmount }))
Expand Down Expand Up @@ -155,7 +135,7 @@ function destroyInstance () {
* @return {Object} Ae instance
*/
const Ae = stampit(Tx, Account, Chain, {
methods: { send, spend, transferFunds, destroyInstance, resolveRecipientName, signUsingGA },
methods: { send, spend, transferFunds, destroyInstance, signUsingGA },
deepProps: { Ae: { defaults: { denomination: AE_AMOUNT_FORMATS.AETTOS } } },
deepConfiguration: { Ae: { methods: ['signUsingGA'] } }
})
Expand Down
2 changes: 1 addition & 1 deletion es/chain/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const Chain = Oracle.compose({
Ae: {
methods: [
'sendTransaction', 'height', 'awaitHeight', 'poll', 'balance', 'getBalance', 'tx',
'mempool', 'topBlock', 'getTxInfo', 'txDryRun', 'getName', 'getNodeInfo', 'getAccount', 'getContractByteCode', 'getContract', 'waitForTxConfirm'
'mempool', 'topBlock', 'getTxInfo', 'txDryRun', 'getName', 'getNodeInfo', 'getAccount', 'getContractByteCode', 'getContract', 'waitForTxConfirm', 'resolveName'
]
}
}
Expand Down
33 changes: 32 additions & 1 deletion es/chain/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import Oracle from '../oracle/node'
import { AE_AMOUNT_FORMATS, formatAmount } from '../utils/amount-formatter'
import TransactionValidator from '../tx/validator'
import NodePool from '../node-pool'
import { assertedType } from '../utils/crypto'
import { isNameValid, produceNameId } from '../tx/builder/helpers'
import { NAME_ID_KEY } from '../tx/builder/schema'

/**
* ChainNode module
Expand Down Expand Up @@ -206,6 +209,33 @@ async function getName (name) {
return this.api.getNameEntryByName(name)
}

/**
* Resolve AENS name and return name hash
* @param {String} nameOrId
* @param {String} prefix
* @param {Boolean} verify
* @param {Boolean} resolveByNode
* @return {String} Address or AENS name hash
*/
async function resolveName (nameOrId, prefix, { verify = false, resolveByNode = false } = {}) {
const prefixes = Object.keys(NAME_ID_KEY)
if (!nameOrId || typeof nameOrId !== 'string') throw new Error('Invalid name or address. Should be a string')
if (!prefixes.includes(prefix)) throw new Error(`Invalid prefix ${prefix}. Should be one of [${prefixes}]`)
if (assertedType(nameOrId, prefix, true)) return nameOrId

if (isNameValid(nameOrId)) {
if (resolveByNode || verify) {
const name = await this.getName(nameOrId).catch(_ => null)
if (!name) throw new Error('Name not found')
const pointer = name.pointers.find(({ id }) => id.split('_')[0] === prefix)
if (!pointer) throw new Error(`Name ${nameOrId} do not have pointers for ${prefix}`)
return pointer.id
}
return produceNameId(nameOrId)
}
throw new Error('Invalid name or address')
}

/**
* ChainNode Stamp
*
Expand Down Expand Up @@ -243,7 +273,8 @@ const ChainNode = Chain.compose(Oracle, TransactionValidator, NodePool, {
getContractByteCode,
getContract,
getName,
waitForTxConfirm
waitForTxConfirm,
resolveName
}
})

Expand Down
2 changes: 1 addition & 1 deletion es/contract/aci/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const prepareArgsForEncode = prepareArgs
*/
async function getContractInstance (source, { aci, contractAddress, filesystem = {}, forceCodeCheck = true, opt } = {}) {
aci = aci || await this.contractGetACI(source, { filesystem })
if (contractAddress) contractAddress = await this.resolveContractAddress(contractAddress)
if (contractAddress) contractAddress = await this.resolveName(contractAddress, 'ct', { resolveByNode: true })
const defaultOptions = {
skipArgsConvert: false,
skipTransformDecoded: false,
Expand Down
13 changes: 3 additions & 10 deletions es/tx/builder/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
NAME_FEE_BID_INCREMENT,
NAME_BID_TIMEOUTS,
FATE_ABI,
VM_TYPE
VM_TYPE, NAME_ID_KEY
} from './schema'
import { ceil } from '../../utils/bignumber'

Expand Down Expand Up @@ -279,20 +279,13 @@ export function isNameValid (name, throwError = true) {
* returns the type, or throws an exception if type not found.
*/
export function classify (s) {
const keys = {
ak: 'account_pubkey',
ok: 'oracle_pubkey',
ct: 'contract_pubkey',
ch: 'channel'
}

if (!s.match(/^[a-z]{2}_.+/)) {
throw Error('Not a valid hash')
}

const klass = s.substr(0, 2)
if (klass in keys) {
return keys[klass]
if (klass in NAME_ID_KEY) {
return NAME_ID_KEY[klass]
} else {
throw Error(`Unknown class ${klass}`)
}
Expand Down
6 changes: 6 additions & 0 deletions es/tx/builder/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export const NAME_FEE_BID_INCREMENT = 0.05 // # the increment is in percentage
// # see https://github.com/aeternity/aeternity/blob/72e440b8731422e335f879a31ecbbee7ac23a1cf/apps/aecore/src/aec_governance.erl#L272
export const NAME_BID_TIMEOUT_BLOCKS = 480 // # ~1 day
export const NAME_BID_MAX_LENGTH = 12 // # this is the max length for a domain to be part of a bid
export const NAME_ID_KEY = {
ak: 'account_pubkey',
ok: 'oracle_pubkey',
ct: 'contract_pubkey',
ch: 'channel'
}
// # ref: https://github.com/aeternity/aeternity/blob/72e440b8731422e335f879a31ecbbee7ac23a1cf/apps/aecore/src/aec_governance.erl#L290
// # bid ranges:
export const NAME_BID_RANGES = {
Expand Down
2 changes: 1 addition & 1 deletion test/integration/aens.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe('Aens', function () {
try {
await aens.spend(100, name, { onAccount, verify: true })
} catch (e) {
e.message.should.be.equal(`Name ${name} do not have pointers for account`)
e.message.should.be.equal(`Name ${name} do not have pointers for ak`)
}
})
it('Call contract using AENS name', async () => {
Expand Down

0 comments on commit 289598c

Please sign in to comment.