From a568a2fd2880cb043140b9d3b531b31e019532e9 Mon Sep 17 00:00:00 2001 From: lbqds Date: Tue, 19 Nov 2024 13:17:14 +0800 Subject: [PATCH 1/3] Introduce DappTransactionBuilder --- packages/web3/src/codec/instr-codec.test.ts | 16 ++ packages/web3/src/codec/instr-codec.ts | 57 ++++- packages/web3/src/contract/dapp-tx-builder.ts | 210 ++++++++++++++++++ packages/web3/src/contract/index.ts | 1 + packages/web3/src/contract/ralph.ts | 53 +---- 5 files changed, 287 insertions(+), 50 deletions(-) create mode 100644 packages/web3/src/contract/dapp-tx-builder.ts diff --git a/packages/web3/src/codec/instr-codec.test.ts b/packages/web3/src/codec/instr-codec.test.ts index 7fdb389d5..39aadeb7c 100644 --- a/packages/web3/src/codec/instr-codec.test.ts +++ b/packages/web3/src/codec/instr-codec.test.ts @@ -233,4 +233,20 @@ describe('Encode & decode instrs', function () { expect(instrCodec.decode(encoded)).toEqual(instr) }) }) + + it('should check the numeric bounds', () => { + expect(() => instr.toU256(0n)).not.toThrow() + expect(() => instr.toU256(1n)).not.toThrow() + expect(() => instr.toU256((1n << 256n) - 1n)).not.toThrow() + expect(() => instr.toU256(-1n)).toThrow('Invalid u256') + expect(() => instr.toU256(1n << 256n)).toThrow('Invalid u256') + + expect(() => instr.toI256(0n)).not.toThrow() + expect(() => instr.toI256(-1n)).not.toThrow() + expect(() => instr.toI256(1n)).not.toThrow() + expect(() => instr.toI256(-(1n << 255n))).not.toThrow() + expect(() => instr.toI256((1n << 255n) - 1n)).not.toThrow() + expect(() => instr.toI256(1n << 255n)).toThrow('Invalid i256') + expect(() => instr.toI256(-(1n << 255n) - 1n)).toThrow('Invalid i256') + }) }) diff --git a/packages/web3/src/codec/instr-codec.ts b/packages/web3/src/codec/instr-codec.ts index d25509949..f3d45ef11 100644 --- a/packages/web3/src/codec/instr-codec.ts +++ b/packages/web3/src/codec/instr-codec.ts @@ -20,7 +20,7 @@ import { ArrayCodec } from './array-codec' import { i256Codec, u256Codec, i32Codec } from './compact-int-codec' import { ByteString, byteStringCodec, byteStringsCodec } from './bytestring-codec' import { LockupScript, lockupScriptCodec } from './lockup-script-codec' -import { byteCodec, Codec } from './codec' +import { assert, byteCodec, Codec } from './codec' import { intAs4BytesCodec } from './int-as-4bytes-codec' import { Reader } from './reader' export type Instr = @@ -1263,3 +1263,58 @@ export class InstrCodec extends Codec { } export const instrCodec = new InstrCodec() export const instrsCodec = new ArrayCodec(instrCodec) + +function checkU256(number: bigint) { + if (number < 0n || number >= 2n ** 256n) { + throw new Error(`Invalid u256 number: ${number}`) + } +} + +export function toU256(number: bigint) { + checkU256(number) + switch (number) { + case 0n: + return U256Const0 + case 1n: + return U256Const1 + case 2n: + return U256Const2 + case 3n: + return U256Const3 + case 4n: + return U256Const4 + case 5n: + return U256Const5 + default: + return U256Const(number) + } +} + +function checkI256(number: bigint) { + const upperBound = 2n ** 255n + if (number < -upperBound || number >= upperBound) { + throw new Error(`Invalid i256 number: ${number}`) + } +} + +export function toI256(number: bigint) { + checkI256(number) + switch (number) { + case 0n: + return I256Const0 + case 1n: + return I256Const1 + case 2n: + return I256Const2 + case 3n: + return I256Const3 + case 4n: + return I256Const4 + case 5n: + return I256Const5 + case -1n: + return I256ConstN1 + default: + return I256Const(number) + } +} diff --git a/packages/web3/src/contract/dapp-tx-builder.ts b/packages/web3/src/contract/dapp-tx-builder.ts new file mode 100644 index 000000000..c11eb7bb2 --- /dev/null +++ b/packages/web3/src/contract/dapp-tx-builder.ts @@ -0,0 +1,210 @@ +/* +Copyright 2018 - 2022 The Alephium Authors +This file is part of the alephium project. + +The library is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with the library. If not, see . +*/ + +import { contractIdFromAddress } from '../address' +import { Val } from '../api' +import { + AddressConst, + ApproveAlph, + ApproveToken, + BytesConst, + CallExternal, + ConstFalse, + ConstTrue, + Dup, + Instr, + Pop, + toI256, + toU256, + U256Const, + Method +} from '../codec' +import { LockupScript, lockupScriptCodec } from '../codec/lockup-script-codec' +import { scriptCodec } from '../codec/script-codec' +import { ALPH_TOKEN_ID } from '../constants' +import { TraceableError } from '../error' +import { SignExecuteScriptTxParams } from '../signer' +import { base58ToBytes, binToHex, HexString, hexToBinUnsafe, isBase58, isHexString } from '../utils' + +export class DappTransactionBuilder { + private callerLockupScript: LockupScript + private approvedAssets: Map + private instrs: Instr[] + + constructor(public readonly callerAddress: string) { + try { + this.callerLockupScript = lockupScriptCodec.decode(base58ToBytes(this.callerAddress)) + if (this.callerLockupScript.kind !== 'P2PKH' && this.callerLockupScript.kind !== 'P2SH') { + throw new Error(`Expected a P2PKH address or P2SH address`) + } + } catch (error) { + throw new TraceableError(`Invalid caller address: ${callerAddress}`, error) + } + this.approvedAssets = new Map() + this.instrs = [] + } + + callContract(params: { + contractAddress: string + methodIndex: number + args: Val[] + attoAlphAmount?: bigint + tokens?: { id: HexString; amount: bigint }[] + retLength?: number + }) { + if (!isBase58(params.contractAddress)) { + throw new Error(`Invalid contract address: ${params.contractAddress}, expected a base58 string`) + } + const contractLockupScript = lockupScriptCodec.decode(base58ToBytes(params.contractAddress)) + if (contractLockupScript.kind !== 'P2C') { + throw new Error(`Invalid contract address: ${params.contractAddress}, expected a P2C address`) + } + + if (params.methodIndex < 0) { + throw new Error(`Invalid method index: ${params.methodIndex}`) + } + + const allTokens = (params.tokens ?? []).concat([{ id: ALPH_TOKEN_ID, amount: params.attoAlphAmount ?? 0n }]) + const instrs = [ + ...genApproveAssets(this.callerLockupScript, this.approveTokens(allTokens)), + ...genContractCall(params.contractAddress, params.methodIndex, params.args, params.retLength ?? 0) + ] + this.instrs.push(...instrs) + return this + } + + getResult(): SignExecuteScriptTxParams { + const method: Method = { + isPublic: true, + usePreapprovedAssets: this.approvedAssets.size > 0, + useContractAssets: false, + usePayToContractOnly: false, + argsLength: 0, + localsLength: 0, + returnLength: 0, + instrs: this.instrs + } + const script = { methods: [method] } + const bytecode = scriptCodec.encode(script) + const tokens = Array.from(this.approvedAssets.entries()).map(([id, amount]) => ({ id, amount })) + this.approvedAssets.clear() + this.instrs = [] + return { + signerAddress: this.callerAddress, + signerKeyType: this.callerLockupScript.kind === 'P2PKH' ? 'default' : 'bip340-schnorr', + bytecode: binToHex(bytecode), + attoAlphAmount: tokens.find((t) => t.id === ALPH_TOKEN_ID)?.amount, + tokens: tokens.filter((t) => t.id !== ALPH_TOKEN_ID) + } + } + + private addTokenToMap(tokenId: HexString, amount: bigint, map: Map) { + const current = map.get(tokenId) + if (current !== undefined) { + map.set(tokenId, current + amount) + } else if (amount > 0n) { + map.set(tokenId, amount) + } + } + + private approveTokens(tokens: { id: HexString; amount: bigint }[]): { id: HexString; amount: bigint }[] { + const tokenAmounts = new Map() + tokens.forEach((token) => { + if (!(isHexString(token.id) && token.id.length === 64)) { + throw new Error(`Invalid token id: ${token.id}`) + } + if (token.amount < 0n) { + throw new Error(`Invalid token amount: ${token.amount}`) + } + this.addTokenToMap(token.id, token.amount, tokenAmounts) + this.addTokenToMap(token.id, token.amount, this.approvedAssets) + }) + return Array.from(tokenAmounts.entries()).map(([id, amount]) => ({ id, amount })) + } +} + +function genApproveAssets(callerLockupScript: LockupScript, tokens: { id: HexString; amount: bigint }[]): Instr[] { + if (tokens.length === 0) { + return [] + } + const approveInstrs = tokens.flatMap((token) => { + if (token.id === ALPH_TOKEN_ID) { + return [U256Const(token.amount), ApproveAlph] + } else { + const tokenId = BytesConst(hexToBinUnsafe(token.id)) + return [tokenId, U256Const(token.amount), ApproveToken] + } + }) + return [ + AddressConst(callerLockupScript), + ...Array.from(Array(tokens.length - 1).keys()).map(() => Dup), + ...approveInstrs + ] +} + +function bigintToNumeric(value: bigint): Instr { + return value >= 0 ? toU256(value) : toI256(value) +} + +function strToNumeric(str: string): Instr { + const regex = /^-?\d+[ui]?$/ + if (regex.test(str)) { + if (str.endsWith('i')) return toI256(BigInt(str.slice(0, str.length - 1))) + if (str.endsWith('u')) return toU256(BigInt(str.slice(0, str.length - 1))) + return bigintToNumeric(BigInt(str)) + } + throw new Error(`Invalid number: ${str}`) +} + +function toAddressOpt(str: string): LockupScript | undefined { + if (!isBase58(str)) return undefined + try { + return lockupScriptCodec.decode(base58ToBytes(str)) + } catch (_) { + return undefined + } +} + +export function genArgs(args: Val[]): Instr[] { + return args.flatMap((arg) => { + if (typeof arg === 'boolean') return arg ? [ConstTrue] : [ConstFalse] + if (typeof arg === 'bigint') return bigintToNumeric(arg) + if (typeof arg === 'string') { + if (isHexString(arg)) return [BytesConst(hexToBinUnsafe(arg))] + const addressOpt = toAddressOpt(arg) + if (addressOpt !== undefined) return AddressConst(addressOpt) + return strToNumeric(arg) + } + if (Array.isArray(arg)) return genArgs(arg) + if (arg instanceof Map) throw new Error(`Map cannot be used as a function argument`) + if (typeof arg === 'object') return genArgs(Object.values(arg)) + throw new Error(`Unknown argument type: ${typeof arg}, arg: ${arg}`) + }) +} + +function genContractCall(contractAddress: string, methodIndex: number, args: Val[], retLength: number): Instr[] { + const argInstrs = genArgs(args) + return [ + ...argInstrs, + toU256(BigInt(argInstrs.length)), + toU256(BigInt(retLength)), + BytesConst(contractIdFromAddress(contractAddress)), + CallExternal(methodIndex), + ...Array.from(Array(retLength).keys()).map(() => Pop) + ] +} diff --git a/packages/web3/src/contract/index.ts b/packages/web3/src/contract/index.ts index 65618310c..64086d39d 100644 --- a/packages/web3/src/contract/index.ts +++ b/packages/web3/src/contract/index.ts @@ -21,3 +21,4 @@ export * from './contract' export * from './events' export * from './script-simulator' export * from './deployment' +export { DappTransactionBuilder } from './dapp-tx-builder' diff --git a/packages/web3/src/contract/ralph.ts b/packages/web3/src/contract/ralph.ts index 17e1ab448..86677b316 100644 --- a/packages/web3/src/contract/ralph.ts +++ b/packages/web3/src/contract/ralph.ts @@ -26,24 +26,11 @@ import { ConstFalse, ConstTrue, i256Codec, - I256Const, - I256Const0, - I256Const1, - I256Const2, - I256Const3, - I256Const4, - I256Const5, - I256ConstN1, i32Codec, instrCodec, u256Codec, - U256Const, - U256Const0, - U256Const1, - U256Const2, - U256Const3, - U256Const4, - U256Const5 + toU256, + toI256 } from '../codec' import { boolCodec } from '../codec/codec' import { TraceableError } from '../error' @@ -118,43 +105,11 @@ function invalidScriptField(tpe: string, value: Val): Error { } function encodeScriptFieldI256(value: bigint): Uint8Array { - switch (value) { - case 0n: - return instrCodec.encode(I256Const0) - case 1n: - return instrCodec.encode(I256Const1) - case 2n: - return instrCodec.encode(I256Const2) - case 3n: - return instrCodec.encode(I256Const3) - case 4n: - return instrCodec.encode(I256Const4) - case 5n: - return instrCodec.encode(I256Const5) - case -1n: - return instrCodec.encode(I256ConstN1) - default: - return instrCodec.encode(I256Const(value)) - } + return instrCodec.encode(toI256(value)) } function encodeScriptFieldU256(value: bigint): Uint8Array { - switch (value) { - case 0n: - return instrCodec.encode(U256Const0) - case 1n: - return instrCodec.encode(U256Const1) - case 2n: - return instrCodec.encode(U256Const2) - case 3n: - return instrCodec.encode(U256Const3) - case 4n: - return instrCodec.encode(U256Const4) - case 5n: - return instrCodec.encode(U256Const5) - default: - return instrCodec.encode(U256Const(value)) - } + return instrCodec.encode(toU256(value)) } export function encodeScriptFieldAsString(tpe: string, value: Val): string { From e154bcbb509240f83bb41ca0b21b83495ab834fd Mon Sep 17 00:00:00 2001 From: lbqds Date: Tue, 19 Nov 2024 13:17:29 +0800 Subject: [PATCH 2/3] Test DappTransactionBuilder --- .../web3/src/contract/dapp-tx-builder.test.ts | 157 ++++++++++++++++++ test/transaction.test.ts | 77 ++++++++- 2 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 packages/web3/src/contract/dapp-tx-builder.test.ts diff --git a/packages/web3/src/contract/dapp-tx-builder.test.ts b/packages/web3/src/contract/dapp-tx-builder.test.ts new file mode 100644 index 000000000..59fdc9687 --- /dev/null +++ b/packages/web3/src/contract/dapp-tx-builder.test.ts @@ -0,0 +1,157 @@ +/* +Copyright 2018 - 2022 The Alephium Authors +This file is part of the alephium project. + +The library is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with the library. If not, see . +*/ + +import { randomContractAddress, randomContractId, testAddress } from '@alephium/web3-test' +import { DappTransactionBuilder, genArgs } from './dapp-tx-builder' +import { + AddressConst, + BytesConst, + ConstFalse, + ConstTrue, + I256Const, + I256Const1, + I256ConstN1, + U256Const1, + U256Const2 +} from '../codec' +import { base58ToBytes, hexToBinUnsafe } from '../utils' +import { lockupScriptCodec } from '../codec/lockup-script-codec' +import { ALPH_TOKEN_ID, ONE_ALPH } from '../constants' + +describe('dapp-tx-builder', function () { + it('should gen code for args', () => { + expect(genArgs(['1i', '2u', '-1', '2'])).toEqual([I256Const1, U256Const2, I256ConstN1, U256Const2]) + + expect(genArgs([false, 1n, -1n, '0011', testAddress])).toEqual([ + ConstFalse, + U256Const1, + I256ConstN1, + BytesConst(hexToBinUnsafe('0011')), + AddressConst(lockupScriptCodec.decode(base58ToBytes(testAddress))) + ]) + + expect(genArgs([false, [1n, 2n], ['0011', '2233']])).toEqual([ + ConstFalse, + U256Const1, + U256Const2, + BytesConst(hexToBinUnsafe('0011')), + BytesConst(hexToBinUnsafe('2233')) + ]) + + expect(genArgs([true, { array0: [1n, 2n], array1: [-1n, -2n] }])).toEqual([ + ConstTrue, + U256Const1, + U256Const2, + I256ConstN1, + I256Const(-2n) + ]) + + expect(() => genArgs(['1234a'])).toThrow('Invalid number') + expect(() => genArgs([2n ** 256n])).toThrow('Invalid u256 number') + expect(() => genArgs([-(2n ** 256n)])).toThrow('Invalid i256 number') + expect(() => genArgs([new Map()])).toThrow('Map cannot be used as a function argument') + }) + + it('should build dapp txs', () => { + expect(() => new DappTransactionBuilder(randomContractAddress())).toThrow('Invalid caller address') + expect(() => new DappTransactionBuilder('Il')).toThrow('Invalid caller address') + + const builder = new DappTransactionBuilder(testAddress) + expect(() => + builder.callContract({ + contractAddress: testAddress, + methodIndex: 0, + args: [] + }) + ).toThrow('Invalid contract address') + + expect(() => + builder.callContract({ + contractAddress: randomContractAddress(), + methodIndex: -1, + args: [] + }) + ).toThrow('Invalid method index') + + const commonParams = { contractAddress: randomContractAddress(), methodIndex: 0, args: [] } + expect(() => + builder.callContract({ + ...commonParams, + tokens: [{ id: 'invalid id', amount: 1n }] + }) + ).toThrow('Invalid token id') + + expect(() => + builder.callContract({ + ...commonParams, + tokens: [{ id: randomContractId().slice(0, 60), amount: 1n }] + }) + ).toThrow('Invalid token id') + + expect(() => + builder.callContract({ + ...commonParams, + tokens: [{ id: ALPH_TOKEN_ID, amount: -1n }] + }) + ).toThrow('Invalid token amount') + + const result0 = builder.callContract({ ...commonParams }).getResult() + expect(result0.attoAlphAmount).toEqual(undefined) + expect(result0.tokens).toEqual([]) + + const tokenId0 = randomContractId() + const tokenId1 = randomContractId() + const result1 = builder + .callContract({ + ...commonParams, + attoAlphAmount: ONE_ALPH, + tokens: [ + { id: ALPH_TOKEN_ID, amount: ONE_ALPH }, + { id: tokenId0, amount: ONE_ALPH }, + { id: tokenId1, amount: 0n } + ] + }) + .getResult() + expect(result1.attoAlphAmount).toEqual(ONE_ALPH * 2n) + expect(result1.tokens).toEqual([{ id: tokenId0, amount: ONE_ALPH }]) + + const result2 = builder + .callContract({ + ...commonParams, + attoAlphAmount: 0n, + tokens: [ + { id: tokenId0, amount: 0n }, + { id: tokenId1, amount: ONE_ALPH } + ] + }) + .callContract({ + ...commonParams, + attoAlphAmount: ONE_ALPH, + tokens: [ + { id: tokenId0, amount: ONE_ALPH }, + { id: tokenId1, amount: ONE_ALPH } + ] + }) + .getResult() + expect(result2.attoAlphAmount).toEqual(ONE_ALPH) + expect(result2.tokens).toEqual([ + { id: tokenId1, amount: ONE_ALPH * 2n }, + { id: tokenId0, amount: ONE_ALPH } + ]) + }) +}) diff --git a/test/transaction.test.ts b/test/transaction.test.ts index 8724782a2..682845b7e 100644 --- a/test/transaction.test.ts +++ b/test/transaction.test.ts @@ -16,8 +16,8 @@ You should have received a copy of the GNU Lesser General Public License along with the library. If not, see . */ -import { SignTransferChainedTxParams, subscribeToTxStatus } from '../packages/web3' -import { node, ONE_ALPH, DUST_AMOUNT } from '../packages/web3' +import { DappTransactionBuilder, SignTransferChainedTxParams, subscribeToTxStatus } from '../packages/web3' +import { node, ONE_ALPH, DUST_AMOUNT, MINIMAL_CONTRACT_DEPOSIT } from '../packages/web3' import { SubscribeOptions, sleep } from '../packages/web3' import { web3 } from '../packages/web3' import { TxStatus } from '../packages/web3' @@ -212,7 +212,7 @@ describe('transactions', function () { expect(BigInt(contractBalance.balance)).toBe(ONE_ALPH) }) - it.only('should build chain txs that interact with dApp in another group', async () => { + it('should build chain txs that interact with dApp in another group', async () => { const nodeProvider = web3.getCurrentNodeProvider() const [wallet, account1, account2] = await prepareChainedTxTest() @@ -322,6 +322,77 @@ describe('transactions', function () { ).rejects.toThrow('Unmatched public key') }) + it('should build dapp transactions', async () => { + const nodeProvider = web3.getCurrentNodeProvider() + const { tokenId } = await mintToken(signer.address, 10n * 10n ** 18n) + const initBalance = await nodeProvider.addresses.getAddressesAddressBalance(signer.address) + let alphBalance = BigInt(initBalance.balance) + const result0 = await Transact.deploy(signer, { + initialAttoAlphAmount: MINIMAL_CONTRACT_DEPOSIT, + initialFields: { tokenId, totalALPH: 0n, totalTokens: 0n } + }) + const contractAddress0 = result0.contractInstance.address + const builder = new DappTransactionBuilder(signer.address) + const unsignedTx0 = builder + .callContract({ + contractAddress: contractAddress0, + methodIndex: 0, // Transact.depositAlph + args: [], + attoAlphAmount: ONE_ALPH + }) + .getResult() + const tx0 = await signer.signAndSubmitExecuteScriptTx(unsignedTx0) + const balance0 = await nodeProvider.addresses.getAddressesAddressBalance(signer.address) + expect(BigInt(tokenBalance(balance0, tokenId)!)).toEqual(ONE_ALPH * 10n) + expect(BigInt(balance0.balance)).toEqual( + alphBalance - + MINIMAL_CONTRACT_DEPOSIT - + BigInt(result0.gasAmount) * BigInt(result0.gasPrice) - + ONE_ALPH - + BigInt(tx0.gasAmount) * BigInt(tx0.gasPrice) + ) + alphBalance = BigInt(balance0.balance) + + const result1 = await Sub.deploy(signer, { + initialAttoAlphAmount: MINIMAL_CONTRACT_DEPOSIT, + initialFields: { result: 0n } + }) + const contractAddress1 = result1.contractInstance.address + const unsignedTx1 = builder + .callContract({ + contractAddress: contractAddress0, + methodIndex: 0, // Transact.depositAlph + args: [], + attoAlphAmount: ONE_ALPH + }) + .callContract({ + contractAddress: contractAddress0, + methodIndex: 2, // Transact.depositToken + args: [ONE_ALPH], + tokens: [{ id: tokenId, amount: ONE_ALPH }] + }) + .callContract({ + contractAddress: contractAddress1, + methodIndex: 0, // Sub.sub + args: [5n, 1n], + retLength: 1 + }) + .getResult() + const tx1 = await signer.signAndSubmitExecuteScriptTx(unsignedTx1) + const balance1 = await nodeProvider.addresses.getAddressesAddressBalance(signer.address) + expect(BigInt(tokenBalance(balance1, tokenId)!)).toEqual(ONE_ALPH * 9n) + expect(BigInt(balance1.balance)).toEqual( + alphBalance - + MINIMAL_CONTRACT_DEPOSIT - + BigInt(result1.gasAmount) * BigInt(result1.gasPrice) - + ONE_ALPH - + BigInt(tx1.gasAmount) * BigInt(tx1.gasPrice) + ) + + const subContractState = await result1.contractInstance.fetchState() + expect(subContractState.fields.result).toEqual(4n) + }) + function tokenBalance(balance: Balance, tokenId: string): string | undefined { return balance.tokenBalances?.find((t) => t.id === tokenId)?.amount } From 0fb2509eeaa325e1c840f58dee3429500bfba408 Mon Sep 17 00:00:00 2001 From: lbqds Date: Thu, 21 Nov 2024 22:10:38 +0800 Subject: [PATCH 3/3] Address comments --- packages/web3/src/contract/dapp-tx-builder.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/web3/src/contract/dapp-tx-builder.ts b/packages/web3/src/contract/dapp-tx-builder.ts index c11eb7bb2..603e53a22 100644 --- a/packages/web3/src/contract/dapp-tx-builder.ts +++ b/packages/web3/src/contract/dapp-tx-builder.ts @@ -16,7 +16,7 @@ You should have received a copy of the GNU Lesser General Public License along with the library. If not, see . */ -import { contractIdFromAddress } from '../address' +import { contractIdFromAddress, isContractAddress } from '../address' import { Val } from '../api' import { AddressConst, @@ -70,8 +70,7 @@ export class DappTransactionBuilder { if (!isBase58(params.contractAddress)) { throw new Error(`Invalid contract address: ${params.contractAddress}, expected a base58 string`) } - const contractLockupScript = lockupScriptCodec.decode(base58ToBytes(params.contractAddress)) - if (contractLockupScript.kind !== 'P2C') { + if (!isContractAddress(params.contractAddress)) { throw new Error(`Invalid contract address: ${params.contractAddress}, expected a P2C address`) }