Skip to content

Commit

Permalink
Decode contract bytearray in fate bytecode (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk authored Aug 14, 2024
1 parent 4ed4a00 commit 4478f93
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 18 deletions.
29 changes: 13 additions & 16 deletions src/ContractEncoder.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
const RLP = require('rlp')
const ApiEncoder = require('./ApiEncoder')
const Serializer = require('./Serializer')
const BytecodeSerializer = require('./Serializers/BytecodeSerializer')
const {byteArray2Int, byteArray2Hex} = require('./utils/int2ByteArray')
const ContractBytecodeSerializer = require('./Serializers/ContractBytecodeSerializer')
const IntSerializer = require('./Serializers/IntSerializer')
const FateTag = require('./FateTag')

class ContractEncoder {
constructor() {
this._apiEncoder = new ApiEncoder()
this._bytecodeSerializer = new BytecodeSerializer(new Serializer())
this._contractBytecodeSerializer = new ContractBytecodeSerializer(new Serializer())
this._intSerializer = new IntSerializer()
}

/**
Expand All @@ -17,19 +18,15 @@ class ContractEncoder {
* @returns {Object} Decoded contract metadata as POJO.
*/
decode(data) {
const binData = this._apiEncoder.decodeWithType(data, 'contract_bytearray')
const decoded = RLP.decode(binData, true)
const stringDecoder = new TextDecoder()
const bytecode = this._apiEncoder.decodeWithType(data, 'contract_bytearray')

return {
tag: byteArray2Int(decoded.data[0]),
vsn: byteArray2Int(decoded.data[1]),
sourceHash: byteArray2Hex(decoded.data[2]),
aevmTypeInfo: decoded.data[3],
compilerVersion: stringDecoder.decode(decoded.data[5]),
payable: Boolean(decoded.data[6][0]),
bytecode: this._bytecodeSerializer.deserialize(new Uint8Array(decoded.data[4]))
}
const fateContractBytearray = new Uint8Array([
FateTag.CONTRACT_BYTEARRAY,
...this._intSerializer.serialize(bytecode.length),
...bytecode,
])

return this._contractBytecodeSerializer.deserialize(fateContractBytearray)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/FateTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ module.exports = Object.freeze({
EMPTY_STRING : 0b01011111, // 0101 1111
POS_BIG_INT : 0b01101111, // 0110 1111 | RLP encoded (integer - 64)
FALSE : 0b01111111, // 0111 1111
// 1000 1111 - FREE (Possibly for bytecode in the future.)
CONTRACT_BYTEARRAY: 0b10001111, // 1000 1111
OBJECT : 0b10011111, // 1001 1111 | ObjectType | RLP encoded Array
VARIANT : 0b10101111, // 1010 1111 | [encoded arities] | encoded tag | [encoded values]
MAP_ID : 0b10111111, // 1011 1111 | RLP encoded integer (store map id)
Expand Down
5 changes: 5 additions & 0 deletions src/FateTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ const FateTypeCalldata = (functionName, argumentTypes) => {
}
}

const FateTypeContractBytearray = () => {
return {name: 'contract_bytearray'}
}

const FateTypeVar = (id) => {
return {
name: 'tvar',
Expand Down Expand Up @@ -293,6 +297,7 @@ module.exports = {
FateTypeBls12381Fr,
FateTypeBls12381Fp,
FateTypeCalldata,
FateTypeContractBytearray,
FateTypeVar,
FateTypeAny,
}
2 changes: 2 additions & 0 deletions src/Serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const TupleSerializer = require('./Serializers/TupleSerializer')
const VariantSerializer = require('./Serializers/VariantSerializer')
const Bls12381FieldSerializer = require('./Serializers/Bls12381FieldSerializer')
const CalldataSerializer = require('./Serializers/CalldataSerializer')
const ContractBytecodeSerializer = require('./Serializers/ContractBytecodeSerializer')
const TypeSerializer = require('./Serializers/TypeSerializer')
const SerializerError = require('./Errors/SerializerError')

Expand Down Expand Up @@ -52,6 +53,7 @@ class Serializer extends BaseSerializer {
'bls12_381.fr': new Bls12381FieldSerializer(),
'bls12_381.fp': new Bls12381FieldSerializer(),
'calldata': new CalldataSerializer(this),
'contract_bytearray': new ContractBytecodeSerializer(this),
'type': new TypeSerializer(),
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Serializers/BytecodeSerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class BytecodeSerializer extends BaseSerializer {
const id = byteArray2Hex(data.slice(1, 5))

if (prefix !== 0xfe) {
throw new Error(`Wrong function prefix, expeted 0xfe got 0x${data[0].toString(16)}`)
throw new Error(`Wrong function prefix, expected 0xfe got 0x${data[0].toString(16)}`)
}

const name = symbols[id]
Expand Down
35 changes: 35 additions & 0 deletions src/Serializers/ContractBytecodeSerializer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const RLP = require('rlp')
const BaseSerializer = require('./BaseSerializer')
const BytecodeSerializer = require('./BytecodeSerializer')
const IntSerializer = require('./IntSerializer')
const {byteArray2Int, byteArray2Hex} = require('../utils/int2ByteArray')

class ContractBytecodeSerializer extends BaseSerializer {
constructor(globalSerializer) {
super()
this._bytecodeSerializer = new BytecodeSerializer(globalSerializer)
this._intSerializer = new IntSerializer()
}

deserializeStream(data) {
const buffer = new Uint8Array(data)
const [fateInt, remainder] = this._intSerializer.deserializeStream(buffer.slice(1))
const overallSize = Number(fateInt)
const decoded = RLP.decode(remainder.slice(0, overallSize))
const stringEncoder = new TextDecoder()
return [
{
tag: byteArray2Int(decoded[0]),
vsn: byteArray2Int(decoded[1]),
sourceHash: byteArray2Hex(decoded[2]),
aevmTypeInfo: decoded[3],
bytecode: this._bytecodeSerializer.deserialize(decoded[4]),
compilerVersion: stringEncoder.decode(decoded[5]),
payable: Boolean(decoded[6][0]),
},
remainder.slice(overallSize),
]
}
}

module.exports = ContractBytecodeSerializer
5 changes: 5 additions & 0 deletions src/TypeFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const {
FateTypeMap,
FateTypeTuple,
FateTypeVariant,
FateTypeContractBytearray,
FateTypeType,
} = require('./FateTypes')

Expand Down Expand Up @@ -80,6 +81,10 @@ class TypeFactory {
return OBJECT_TYPES[obj]
}

if (tag === FateTag.CONTRACT_BYTEARRAY) {
return FateTypeContractBytearray()
}

if (tag === FateTag.TYPE_INTEGER
|| tag === FateTag.TYPE_BOOLEAN
|| tag === FateTag.TYPE_LIST
Expand Down
21 changes: 21 additions & 0 deletions tests/ContractEncoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ test('Decode basic contract', t => {
})
})

test('Decode contract with Chain.create', t => {
t.plan(1)

const contract = encoder.decode('cb_+NBGA6D+x/gUE1YYLmvMJDIzJK2ZFJyOM5sXubwJy+9TVt/ib8C4n7iE/kTWRB8ANwA3ABoOgj8BAz/+m66dXgA3AQdHAgwBAAwDAAwDNwEHDAOPbxX4U0YDoJg7mklGIIWH49uiZBksC7yUEVO88y4D7lTd8+T4TMK2wKOS/kTWRB8ANwEHNwAaBoIAAQM/jC8BEUTWRB8RaW5pdIIvAIk4LjAuMC1yYzEAowAAlS8CEUTWRB8RaW5pdBGbrp1eDW5ld4IvAIk4LjAuMC1yYzEAaSb5ng==')

t.like(contract.bytecode.functions[1].instructions[0][3], {
mnemonic: 'PUSH',
args: [{
mod: 'immediate',
arg: {
tag: 70n,
vsn: 3n,
sourceHash: '983b9a4946208587e3dba264192c0bbc941153bcf32e03ee54ddf3e4f84cc2b6',
aevmTypeInfo: [],
compilerVersion: '8.0.0-rc1',
payable: false
}
}],
})
})

test('Decode full featured contract', t => {
const contract = encoder.decode(testContract.toString())

Expand Down

0 comments on commit 4478f93

Please sign in to comment.