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

Caching ABI in Action for future use #69

Merged
merged 6 commits into from
Aug 28, 2023
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
39 changes: 36 additions & 3 deletions src/chain/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import {
ABISerializableConstructor,
ABISerializableObject,
ABISerializableType,
synthesizeABI,
} from '../serializer/serializable'

import {arrayEquatableEquals} from '../utils'
import {BuiltinTypes} from '../serializer/builtins'
import {BuiltinTypes, getType} from '../serializer/builtins'

import {
ABI,
Expand Down Expand Up @@ -54,6 +55,8 @@ export class Action extends Struct {
/** The ABI-encoded action data. */
@Struct.field('bytes') data!: Bytes

public abi?: ABI

static from(object: ActionType | AnyAction, abi?: ABIDef): Action {
const data = object.data as any
if (!Bytes.isBytes(data)) {
Expand All @@ -70,12 +73,32 @@ export class Action extends Struct {
data: abiEncode({object: data, type, abi}),
}
}
return super.from(object) as Action

const action = super.from(object) as Action
if (abi) {
action.abi = ABI.from(abi)
} else {
const type = getType(data)
if (type) {
action.abi = ABI.from({
...synthesizeABI(type).abi,
actions: [
{
name: action.name,
type: type.abiName,
ricardian_contract: '',
},
],
})
}
}

return action
}

/** Return true if this Action is equal to given action. */
equals(other: ActionType | AnyAction) {
const otherAction = Action.from(other)
const otherAction = Action.from(other, this.abi)
return (
this.account.equals(otherAction.account) &&
this.name.equals(otherAction.name) &&
Expand Down Expand Up @@ -103,4 +126,14 @@ export class Action extends Struct {
return abiDecode({data: this.data, type, abi})
}
}

get decoded() {
if (!this.abi) {
throw new Error('Missing ABI definition when decoding action data')
}
return {
...this.toJSON(),
data: this.decodeData(this.abi),
}
}
}
8 changes: 7 additions & 1 deletion src/chain/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,13 @@ export class Transaction extends TransactionHeader {
return abis
}
}
const resolveAction = (action: AnyAction) => Action.from(action, abiFor(action.account))
const resolveAction = (action: AnyAction) => {
if (action instanceof Action) {
return action
} else {
return Action.from(action, abiFor(action.account))
}
}
const actions = (object.actions || []).map(resolveAction)
const context_free_actions = (object.context_free_actions || []).map(resolveAction)
const transaction = {
Expand Down
198 changes: 194 additions & 4 deletions test/chain.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {assert} from 'chai'

import {
ABI,
ABIDef,
Action,
AnyTransaction,
Expand All @@ -18,6 +19,7 @@ import {
P2P,
PermissionLevel,
PublicKey,
Serializer,
Signature,
SignedTransaction,
Struct,
Expand Down Expand Up @@ -337,19 +339,38 @@ suite('chain', function () {
assert.equal(variant.equals(Int32.from(1)), false)
assert.equal(variant.equals(MyVariant.from('haj')), false)

@Struct.type('my_struct')
class MyStructWithVariant extends Struct {
@Struct.field(MyVariant) field!: MyVariant
}
const action = Action.from({
account: 'foo',
name: 'bar',
authorization: [perm],
data: variant,
data: MyStructWithVariant.from({
field: variant,
}),
})
assert.equal(action.equals(action), true)
assert.equal(
action.equals({
account: 'foo',
name: 'bar',
authorization: [perm],
data: variant,
data: {
field: 'hello',
},
}),
true
)
assert.equal(
action.equals({
account: 'foo',
name: 'bar',
authorization: [perm],
data: {
field: variant,
},
}),
true
)
Expand All @@ -358,7 +379,9 @@ suite('chain', function () {
account: 'foo',
name: 'bar',
authorization: [],
data: variant,
data: {
field: variant,
},
}),
false
)
Expand All @@ -367,7 +390,9 @@ suite('chain', function () {
account: 'foo',
name: 'bar',
authorization: [{actor: 'maa', permission: 'jong'}],
data: variant,
data: {
field: variant,
},
}),
false
)
Expand Down Expand Up @@ -483,6 +508,171 @@ suite('chain', function () {
assert.equal(a1.equals(a3), true)
})

test('action retains abi (abi)', function () {
const abi = {
structs: [{name: 'noop', base: '', fields: []}],
actions: [
{
name: 'noop',
type: 'noop',
ricardian_contract: '',
},
],
}
const action = Action.from(
{
account: 'greymassnoop',
name: 'noop',
authorization: [{actor: 'greymassfuel', permission: 'cosign'}],
data: '',
},
abi
)
assert.instanceOf(action.abi, ABI)
})

test('action can deserialize itself from abi', function () {
const abi = {
structs: [
{
name: 'transfer',
base: '',
fields: [
{
name: 'from',
type: 'name',
},
{
name: 'to',
type: 'name',
},
{
name: 'quantity',
type: 'asset',
},
{
name: 'memo',
type: 'string',
},
],
},
],
actions: [
{
name: 'transfer',
type: 'transfer',
ricardian_contract: '',
},
],
}

const action = Action.from(
{
account: 'eosio.token',
name: 'transfer',
authorization: [{actor: 'foo', permission: 'bar'}],
data: {
from: 'foo',
to: 'bar',
quantity: '1.0000 EOS',
memo: 'hello',
},
},
abi
)
assert.instanceOf(action.abi, ABI)
const decoded = action.decoded
assert.instanceOf(decoded.account, Name)
assert.instanceOf(decoded.name, Name)
assert.instanceOf(decoded.authorization, Array)
assert.instanceOf(decoded.authorization[0], PermissionLevel)
assert.instanceOf(decoded.data.from, Name)
assert.instanceOf(decoded.data.to, Name)
assert.instanceOf(decoded.data.quantity, Asset)
})

test('action retains abi (struct)', function () {
@Struct.type('transfer')
class Transfer extends Struct {
@Struct.field('name') from!: Name
@Struct.field('name') to!: Name
@Struct.field('asset') quantity!: Asset
@Struct.field('string') memo!: string
}

const data = Transfer.from({
from: 'foo',
to: 'bar',
quantity: '1.0000 EOS',
memo: 'hello',
})

const action = Action.from({
authorization: [],
account: 'eosio.token',
name: 'transfer',
data,
})
assert.instanceOf(action.abi, ABI)

const transaction = Transaction.from({
ref_block_num: 0,
ref_block_prefix: 0,
expiration: 0,
actions: [action],
})

assert.instanceOf(transaction.actions[0].abi, ABI)
assert.isTrue(action.equals(transaction.actions[0]))
assert.isTrue(transaction.actions[0].equals(action))
assert.isTrue(
data.equals(
Serializer.decode({
data: transaction.actions[0].data,
abi: transaction.actions[0].abi,
type: String(transaction.actions[0].name),
})
)
)
})

test('action can deserialize itself from struct', function () {
@Struct.type('transfer')
class Transfer extends Struct {
@Struct.field('name') from!: Name
@Struct.field('name') to!: Name
@Struct.field('asset') quantity!: Asset
@Struct.field('string') memo!: string
}
const data = Transfer.from({
from: 'foo',
to: 'bar',
quantity: '1.0000 EOS',
memo: 'hello',
})

const action = Action.from({
authorization: [
{
actor: 'foo',
permission: 'bar',
},
],
account: 'eosio.token',
name: 'transfer',
data,
})
assert.instanceOf(action.abi, ABI)
const decoded = action.decoded
assert.instanceOf(decoded.account, Name)
assert.instanceOf(decoded.name, Name)
assert.instanceOf(decoded.authorization, Array)
assert.instanceOf(decoded.authorization[0], PermissionLevel)
assert.instanceOf(decoded.data.from, Name)
assert.instanceOf(decoded.data.to, Name)
assert.instanceOf(decoded.data.quantity, Asset)
})

test('authority', function () {
const auth = Authority.from({
threshold: 21,
Expand Down
Loading