Skip to content

Commit

Permalink
refactor(agent,cw,namada): type checks for @fadroma/namada pass
Browse files Browse the repository at this point in the history
  • Loading branch information
egasimus committed May 10, 2024
1 parent 01871ea commit 471a809
Show file tree
Hide file tree
Showing 23 changed files with 443 additions and 291 deletions.
20 changes: 11 additions & 9 deletions packages/agent/src/Block.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
/** Fadroma. Copyright (C) 2023 Hack.bg. License: GNU AGPLv3 or custom.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. **/
import { bold } from './Util'
import { assign, bold } from './Util'
import type { Chain, Transaction } from '../index'

/** The building block of a blockchain, as obtained by
* [the `fetchBlock` method of `Connection`](#method-connectionfetchblock)
*
* Contains zero or more transactions. */
export abstract class Block {
constructor (properties: Pick<Block, 'chain'|'height'|'id'|'timestamp'>) {
this.#chain = properties.chain
this.height = properties.height
this.id = properties.id
this.timestamp = properties.timestamp
constructor (
properties: Pick<Block, 'chain'|'height'|'id'|'timestamp'|'transactions'>
) {
this.#chain = properties.chain
assign(this, properties, [ "id", "height", "timestamp", "transactions" ])
}

#chain: Chain
get chain () { return this.#chain }

/** Unique ID of block. */
id: string
id!: string
/** Monotonically incrementing ID of block. */
height: number
height!: number
/** Timestamp of block */
timestamp?: string
timestamp?: string
/** Transactions in block */
transactions!: unknown[]
}

export async function fetchBlock (chain: Chain, ...args: Parameters<Chain["fetchBlock"]>):
Expand Down
42 changes: 14 additions & 28 deletions packages/agent/src/Chain.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
/** Fadroma. Copyright (C) 2023 Hack.bg. License: GNU AGPLv3 or custom.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. **/
import {
Logged, assign, bold, timed
} from './Util'
import {
fetchBalance
} from './dlt/Bank'
import {
Contract,
fetchCodeInstances,
query
} from './compute/Contract'
import {
UploadedCode,
fetchCodeInfo,
} from './compute/Upload'
import {
Block,
fetchBlock,
nextBlock
} from './Block'
import { Logged, assign, bold, timed } from './Util'
import { fetchBalance } from './dlt/Bank'
import { Contract, fetchCodeInstances, query } from './compute/Contract'
import { UploadedCode, fetchCodeInfo, } from './compute/Upload'
import { Block, fetchBlock, nextBlock } from './Block'
import { Connection } from './Connection'
import type {
Address,
Agent,
ChainId,
CodeId,
Connection,
Identity,
Message,
Token,
Uint128,
} from '../index'

export abstract class Chain extends Logged {

static get Connection () {
return Connection
}

constructor (
properties: ConstructorParameters<typeof Logged>[0]
& Pick<Chain, 'chainId'>
Expand All @@ -50,15 +40,11 @@ export abstract class Chain extends Logged {
/** Time to ping for next block. */
blockInterval = 250

/** Get a connection to the API endpoint. */
/** Get a read-only connection to the API endpoint. */
abstract getConnection (): Connection

/** Authenticate with a random identity. */
abstract authenticate (): Promise<Agent>
/** Authenticate with a mnemonic. */
abstract authenticate (mnemonic: string): Promise<Agent>
/** Authenticate with the provided identity. */
abstract authenticate (identity: Identity): Promise<Agent>
/** Authenticate to the chain, obtaining an Agent instance that can send transactions. */
abstract authenticate (properties?: { mnemonic: string }|Identity): Promise<Agent>

/** Get the current block height. */
get height (): Promise<number> {
Expand Down
70 changes: 40 additions & 30 deletions packages/agent/src/Connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from './Util'
import type {
Address, Block, Chain, ChainId, CodeId, Message, Token, Uint128,
UploadStore, UploadedCode, Contract, Into
UploadStore, UploadedCode, Contract, Into, Identity
} from '../index'

/** Represents a remote API endpoint.
Expand All @@ -17,14 +17,12 @@ import type {
export abstract class Connection extends Logged {
constructor (
properties: ConstructorParameters<typeof Logged>[0]
& Pick<Connection, 'chain'|'url'|'api'>
& Pick<Connection, 'chain'|'url'>
& Partial<Pick<Connection, 'alive'>>
) {
super(properties)
this.#chain = properties.chain
this.url = properties.url
this.alive = properties.alive || true
this.api = properties.api
this.#chain = properties.chain
assign(this, properties, [ "url", "alive" ])
this.log.label = [
this.constructor.name,
'(', this[Symbol.toStringTag] ? `(${bold(this[Symbol.toStringTag])})` : null, ')'
Expand All @@ -33,7 +31,12 @@ export abstract class Connection extends Logged {
const chainColor = randomColor({ luminosity: 'dark', seed: this.url })
this.log.label = colors.bgHex(chainColor).whiteBright(` ${this.url} `)
}

get [Symbol.toStringTag] () {
if (this.url) {
const color = randomColor({ luminosity: 'dark', seed: this.url })
return colors.bgHex(color).whiteBright(this.url)
}
}
#chain: Chain
/** Chain to which this connection points. */
get chain (): Chain {
Expand All @@ -43,31 +46,14 @@ export abstract class Connection extends Logged {
get chainId (): ChainId {
return this.chain.chainId
}

/** Connection URL.
*
* The same chain may be accessible via different endpoints, so
* this property contains the URL to which requests are sent. */
url: string
/** Instance of platform SDK. This must be provided in a subclass.
*
* Since most chain SDKs initialize asynchronously, this is usually a `Promise`
* that resolves to an instance of the underlying client class (e.g. `CosmWasmClient` or `SecretNetworkClient`).
*
* Since transaction and query methods are always asynchronous as well, well-behaved
* implementations of Fadroma Agent begin each method that talks to the chain with
* e.g. `const { api } = await this.api`, making sure an initialized platform SDK instance
* is available. */
api: unknown
url!: string
/** Setting this to false stops retries. */
alive: boolean = true

get [Symbol.toStringTag] () {
if (this.url) {
const color = randomColor({ luminosity: 'dark', seed: this.url })
return colors.bgHex(color).whiteBright(this.url)
}
}
/** Chain-specific implementation of fetchBlock. */
abstract fetchBlockImpl (parameters?:
{ height: number }|{ hash: string }
Expand All @@ -77,9 +63,9 @@ export abstract class Connection extends Logged {
Promise<number>
/** Chain-specific implementation of fetchBalance. */
abstract fetchBalanceImpl (parameters: {
token?: string,
address?: string
}): Promise<string|number|bigint>
addresses: Record<Address, string[]>,
parallel?: boolean
}): Promise<Record<Address, Record<string, Uint128>>>
/** Chain-specific implementation of fetchCodeInfo. */
abstract fetchCodeInfoImpl (parameters?: {
codeIds?: CodeId[]
Expand Down Expand Up @@ -108,6 +94,25 @@ export abstract class Connection extends Logged {

/** Extend this class and implement the abstract methods to add support for a new kind of chain. */
export abstract class SigningConnection {
constructor (properties: { chain: Chain, identity: Identity }) {
this.#chain = properties.chain
this.#identity = properties.identity
}
#chain: Chain
/** Chain to which this connection points. */
get chain (): Chain {
return this.#chain
}
get chainId (): ChainId {
return this.chain.chainId
}
#identity: Identity
get identity (): Identity {
return this.#identity
}
get address (): Address {
return this.identity.address!
}
/** Chain-specific implementation of native token transfer. */
abstract sendImpl (parameters: {
outputs: Record<Address, Record<string, Uint128>>,
Expand All @@ -120,14 +125,19 @@ export abstract class SigningConnection {
binary: Uint8Array,
reupload?: boolean,
uploadStore?: UploadStore,
uploadFee?: Token.ICoin[]|'auto',
uploadFee?: Token.IFee
uploadMemo?: string
}): Promise<Partial<UploadedCode & {
chainId: ChainId,
codeId: CodeId
}>>
/** Chain-specific implementation of contract instantiation. */
abstract instantiateImpl (parameters: Partial<Contract> & { initMsg: Into<Message> }):
abstract instantiateImpl (parameters: Partial<Contract> & {
initMsg: Into<Message>
initFee?: Token.IFee
initSend?: Token.ICoin[]
initMemo?: string
}):
Promise<Contract & { address: Address }>
/** Chain-specific implementation of contract transaction. */
abstract executeImpl <T> (parameters: {
Expand Down
8 changes: 5 additions & 3 deletions packages/agent/src/Identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import type { Address } from './Types'

/** A cryptographic identity. */
export class Identity extends Logged {
constructor (properties?: Partial<Identity>) {
constructor (
properties: ConstructorParameters<typeof Logged>[0] & Pick<Identity, 'name'|'address'> = {}
) {
super(properties)
assign(this, properties, ['name', 'address'])
}
/** Display name. */
name?: Address
/** Unique identifier. */
name?: Address
/** Address of account. */
address?: Address
/** Sign some data with the identity's private key. */
sign (doc: any): unknown {
Expand Down
11 changes: 11 additions & 0 deletions packages/agent/src/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,14 @@ export async function timed <T> (
})
return result
}

export async function optionallyParallel <T> (parallel: boolean|undefined, thunks: Array<()=>Promise<T>>) {
if (parallel) {
return await Promise.all(thunks.map(thunk=>thunk()))
}
const results = []
for (const thunk of thunks) {
results.push(await thunk())
}
return results
}
15 changes: 9 additions & 6 deletions packages/agent/src/dlt/Staking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. **/
import { assign } from '../Util'
import type { Connection, Address } from '../../index'
import type { Chain, Address } from '../../index'

export class Validator {
chain?: Connection
address: Address
constructor (properties: Pick<Validator, 'address'> & Partial<Pick<Validator, 'chain'>>) {
this.chain = properties.chain
this.address = properties.address
constructor (properties: Pick<Validator, 'chain'|'address'>) {
this.#chain = properties.chain
assign(this, properties, [ "address" ])
}
#chain: Chain
get chain () {
return this.#chain
}
address!: Address
}
19 changes: 8 additions & 11 deletions packages/agent/stub/StubChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ export class StubChain extends Chain {
}

getConnection (): StubConnection {
return new StubConnection({
chain: this,
url: 'stub',
api: {},
})
return new StubConnection({ chain: this, url: 'stub', })
}
}

Expand All @@ -80,21 +76,22 @@ export class StubConnection extends Connection {
override fetchBlockImpl (): Promise<StubBlock> {
const timestamp = new Date()
return Promise.resolve(new StubBlock({
chain: this.chain,
id: `stub${+timestamp}`,
height: +timestamp,
timestamp: timestamp.toISOString()
chain: this.chain,
id: `stub${+timestamp}`,
height: +timestamp,
timestamp: timestamp.toISOString(),
transactions: []
}))
}

override fetchBalanceImpl (
...args: Parameters<Connection["fetchBalanceImpl"]>
): Promise<string> {
) {
throw new Error('unimplemented!')
//token ??= this.defaultDenom
//const balance = (this.backend.balances.get(address!)||{})[token] ?? 0
//return Promise.resolve(String(balance))
return Promise.resolve('')
return Promise.resolve({})
}

override fetchCodeInfoImpl (
Expand Down
17 changes: 7 additions & 10 deletions packages/agent/stub/StubIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export class StubAgent extends Agent {

getConnection (): StubSigningConnection {
return new StubSigningConnection({
address: this.identity.address!,
backend: this.chain.backend
chain: this.chain,
identity: this.identity,
})
}

Expand All @@ -35,15 +35,12 @@ export class StubAgent extends Agent {
}

export class StubSigningConnection extends SigningConnection {
constructor (properties: Pick<StubSigningConnection, 'backend'|'address'>) {
super()
this.backend = properties.backend
this.address = properties.address
get chain (): StubChain {
return super.chain as unknown as StubChain
}
get backend (): StubBackend {
return this.chain.backend
}

backend: StubBackend

address: Address

async sendImpl (...args: Parameters<SigningConnection["sendImpl"]>): Promise<void> {
const { backend } = this
Expand Down
Loading

0 comments on commit 471a809

Please sign in to comment.