From 40474592087eb72937f4a1aa3995d89d208dbb7e Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Wed, 10 Sep 2025 15:05:20 -0300 Subject: [PATCH 1/8] ref: interface change --- contracts/src/mcms/merkle-proof.ts | 4 +- contracts/src/utils/dict.ts | 11 +- contracts/tests/ccip/helpers/SetUp.ts | 52 +- .../mcms/RBACTimelockBlockFunction.spec.ts | 186 ++++--- .../tests/mcms/RBACTimelockCancel.spec.ts | 2 +- .../tests/mcms/RBACTimelockExecute.spec.ts | 4 +- .../mcms/RBACTimelockScheduleBatch.spec.ts | 4 +- .../mcms/RBACTimelockUpdateDelay.spec.ts | 2 +- contracts/wrappers/ccip/FeeQuoter.ts | 390 ++++++++++++--- contracts/wrappers/ccip/Router.ts | 134 +++-- contracts/wrappers/examples/Counter.ts | 25 +- .../examples/upgrades/UpgradeableCounterV1.ts | 3 +- .../examples/upgrades/UpgradeableCounterV2.ts | 3 +- .../wrappers/lib/access/AccessControl.ts | 92 ++-- .../wrappers/libraries/access/Ownable2Step.ts | 91 ++-- contracts/wrappers/mcms/CallProxy.ts | 15 +- contracts/wrappers/mcms/MCMS.ts | 259 +++++----- contracts/wrappers/mcms/RBACTimelock.ts | 457 +++++++++--------- contracts/wrappers/utils.ts | 6 +- 19 files changed, 1039 insertions(+), 701 deletions(-) diff --git a/contracts/src/mcms/merkle-proof.ts b/contracts/src/mcms/merkle-proof.ts index 441ef30f1..4e97cea54 100644 --- a/contracts/src/mcms/merkle-proof.ts +++ b/contracts/src/mcms/merkle-proof.ts @@ -30,7 +30,7 @@ export function build( const opProofs: OpProofs = Array.from({ length: ops.length }, (_, i) => computeProof(i)) const encodeProof = (v) => beginCell().storeUint(v, 256) - const encodeSignature = (v) => mcms.builder.data.signature.encode(v).asBuilder() + const encodeSignature = (v) => mcms.builder.data.signature.encode(v) return [ { @@ -117,7 +117,7 @@ export function constructLeaves(ops: mcms.Op[], rootMetadata: mcms.RootMetadata) export function leafMetadataPreimage(rootMetadata: mcms.RootMetadata): Cell { return beginCell() .storeUint(mcms.MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA, 256) - .storeBuilder(mcms.builder.data.rootMetadata.encode(rootMetadata).asBuilder()) + .storeBuilder(mcms.builder.data.rootMetadata.encode(rootMetadata)) .endCell() } diff --git a/contracts/src/utils/dict.ts b/contracts/src/utils/dict.ts index aef130f7c..50689ca32 100644 --- a/contracts/src/utils/dict.ts +++ b/contracts/src/utils/dict.ts @@ -1,4 +1,4 @@ -import { Dictionary, DictionaryKeyTypes, DictionaryKey, DictionaryValue } from '@ton/core' +import { Dictionary, DictionaryKeyTypes, DictionaryKey, DictionaryValue, Builder } from '@ton/core' export const loadMap = ( key: DictionaryKey, @@ -21,3 +21,12 @@ export function loadDict(dict: Dictionary return map } + +export type TolkUMap = { + keyLen: number + dict: Dictionary +} + +export function UMapToBuilder(data: TolkUMap): Builder { + return new Builder().storeDict(data.dict).storeUint(data.keyLen, 16) +} diff --git a/contracts/tests/ccip/helpers/SetUp.ts b/contracts/tests/ccip/helpers/SetUp.ts index 7159fbc33..abe744a26 100644 --- a/contracts/tests/ccip/helpers/SetUp.ts +++ b/contracts/tests/ccip/helpers/SetUp.ts @@ -50,28 +50,30 @@ export const setupTestFeeQuoter = async ( // add config for EVM destination result = await feeQuoter.sendUpdateDestChainConfig(deployer.getSender(), { value: toNano('1'), - destChainSelector: CHAINSEL_EVM_TEST_90000001, - config: { - // minimal valid config - isEnabled: true, - maxNumberOfTokensPerMsg: 0, // TODO: - maxDataBytes: 100, - maxPerMsgGasLimit: 100, - destGasOverhead: 0, - destGasPerPayloadByteBase: 0, - destGasPerPayloadByteHigh: 0, - destGasPerPayloadByteThreshold: 0, - destDataAvailabilityOverheadGas: 0, - destGasPerDataAvailabilityByte: 0, - destDataAvailabilityMultiplierBps: 0, - chainFamilySelector: 0, - enforceOutOfOrder: true, - defaultTokenFeeUsdCents: 0, - defaultTokenDestGasOverhead: 0, - defaultTxGasLimit: 1, - gasMultiplierWeiPerEth: 0n, - gasPriceStalenessThreshold: 0, - networkFeeUsdCents: 0, + msg: { + destChainSelector: CHAINSEL_EVM_TEST_90000001, + destChainConfig: { + // minimal valid config + isEnabled: true, + maxNumberOfTokensPerMsg: 0, // TODO: + maxDataBytes: 100, + maxPerMsgGasLimit: 100, + destGasOverhead: 0, + destGasPerPayloadByteBase: 0, + destGasPerPayloadByteHigh: 0, + destGasPerPayloadByteThreshold: 0, + destDataAvailabilityOverheadGas: 0, + destGasPerDataAvailabilityByte: 0, + destDataAvailabilityMultiplierBps: 0, + chainFamilySelector: 0, + enforceOutOfOrder: true, + defaultTokenFeeUsdCents: 0, + defaultTokenDestGasOverhead: 0, + defaultTxGasLimit: 1, + gasMultiplierWeiPerEth: 0n, + gasPriceStalenessThreshold: 0, + networkFeeUsdCents: 0, + }, }, }) @@ -82,8 +84,10 @@ export const setupTestFeeQuoter = async ( // configure the feeToken result = await feeQuoter.sendUpdateFeeTokens(deployer.getSender(), { value: toNano('1'), - add: [{ token: ZERO_ADDRESS, premiumMultiplier: 1n }], - remove: [], + msg: { + add: new Map([[ZERO_ADDRESS, { premiumMultiplierWeiPerEth: 1n }]]), + remove: [], + }, }) expect(result.transactions).toHaveTransaction({ to: feeQuoter.address, diff --git a/contracts/tests/mcms/RBACTimelockBlockFunction.spec.ts b/contracts/tests/mcms/RBACTimelockBlockFunction.spec.ts index abb159901..a8c386ae9 100644 --- a/contracts/tests/mcms/RBACTimelockBlockFunction.spec.ts +++ b/contracts/tests/mcms/RBACTimelockBlockFunction.spec.ts @@ -23,10 +23,12 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { it('should fail if not admin tries to block function selector', async () => { // Try to block with proposer role (should fail) - const body = rbactl.builder.message.in.blockFunctionSelector.encode({ - queryId: 1n, - selector: counter.opcodes.in.IncreaseCount, - }) + const body = rbactl.builder.message.in.blockFunctionSelector + .encode({ + queryId: 1n, + selector: counter.opcodes.in.IncreaseCount, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -52,13 +54,15 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -75,10 +79,12 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { // Block function selector { - const blockBody = rbactl.builder.message.in.blockFunctionSelector.encode({ - queryId: 1n, - selector: counter.opcodes.in.IncreaseCount, - }) + const blockBody = rbactl.builder.message.in.blockFunctionSelector + .encode({ + queryId: 1n, + selector: counter.opcodes.in.IncreaseCount, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.admin.getSender(), @@ -103,8 +109,8 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { const functionSelectorBlockedMsg = functionSelectorBlockedTx[0].inMessage! const opcode = functionSelectorBlockedMsg.body.beginParse().preloadUint(32) - const blockedConfirmation = rbactl.builder.message.out.functionSelectorBlocked.decode( - functionSelectorBlockedMsg.body, + const blockedConfirmation = rbactl.builder.message.out.functionSelectorBlocked.load( + functionSelectorBlockedMsg.body.beginParse(), ) expect(opcode.toString(16)).toEqual(rbactl.opcodes.out.FunctionSelectorBlocked.toString(16)) @@ -121,13 +127,15 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -149,10 +157,12 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { // Block zero function selector { - const blockBody = rbactl.builder.message.in.blockFunctionSelector.encode({ - queryId: 1n, - selector: zeroSelector, - }) + const blockBody = rbactl.builder.message.in.blockFunctionSelector + .encode({ + queryId: 1n, + selector: zeroSelector, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.admin.getSender(), @@ -176,13 +186,15 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -207,13 +219,15 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -238,13 +252,15 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -268,13 +284,15 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -293,10 +311,12 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { it('should unblock function selector', async () => { // Block Function { - const blockBody = rbactl.builder.message.in.blockFunctionSelector.encode({ - queryId: 1n, - selector: counter.opcodes.in.IncreaseCount, - }) + const blockBody = rbactl.builder.message.in.blockFunctionSelector + .encode({ + queryId: 1n, + selector: counter.opcodes.in.IncreaseCount, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.admin.getSender(), @@ -320,13 +340,15 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -344,10 +366,12 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { // Unblock Function { - const unblockBody = rbactl.builder.message.in.unblockFunctionSelector.encode({ - queryId: 1n, - selector: counter.opcodes.in.IncreaseCount, - }) + const unblockBody = rbactl.builder.message.in.unblockFunctionSelector + .encode({ + queryId: 1n, + selector: counter.opcodes.in.IncreaseCount, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.admin.getSender(), @@ -372,8 +396,8 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { const functionSelectorUnblockedMsg = functionSelectorUnblockedTx[0].inMessage! const opcode = functionSelectorUnblockedMsg.body.beginParse().preloadUint(32) - const unblockedConfirmation = rbactl.builder.message.out.functionSelectorUnblocked.decode( - functionSelectorUnblockedMsg.body, + const unblockedConfirmation = rbactl.builder.message.out.functionSelectorUnblocked.load( + functionSelectorUnblockedMsg.body.beginParse(), ) expect(opcode.toString(16)).toEqual(rbactl.opcodes.out.FunctionSelectorUnblocked.toString(16)) @@ -390,13 +414,15 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), diff --git a/contracts/tests/mcms/RBACTimelockCancel.spec.ts b/contracts/tests/mcms/RBACTimelockCancel.spec.ts index 5cbdf5df3..827c5619c 100644 --- a/contracts/tests/mcms/RBACTimelockCancel.spec.ts +++ b/contracts/tests/mcms/RBACTimelockCancel.spec.ts @@ -208,7 +208,7 @@ describe('MCMS - RBACTimelockCancelTest', () => { const cancelMsg = cancelTx[0].inMessage! const opcode = cancelMsg.body.beginParse().preloadUint(32) - const canceledConfirmation = rbactl.builder.message.out.canceled.decode(cancelMsg.body) + const canceledConfirmation = rbactl.builder.message.out.canceled.load(cancelMsg.body) expect(opcode.toString(16)).toEqual(rbactl.opcodes.out.Canceled.toString(16)) expect(canceledConfirmation.queryId).toEqual(1) diff --git a/contracts/tests/mcms/RBACTimelockExecute.spec.ts b/contracts/tests/mcms/RBACTimelockExecute.spec.ts index d5c3598aa..4870197b9 100644 --- a/contracts/tests/mcms/RBACTimelockExecute.spec.ts +++ b/contracts/tests/mcms/RBACTimelockExecute.spec.ts @@ -163,7 +163,7 @@ describe('MCMS - RBACTimelockExecuteTest', () => { ) const opcode = bypasserExecutedExternal.body.beginParse().preloadUint(32) - const bypasserExecutedEvent = rbactl.builder.message.out.bypasserCallExecuted.decode( + const bypasserExecutedEvent = rbactl.builder.message.out.bypasserCallExecuted.load( bypasserExecutedExternal.body, ) @@ -485,7 +485,7 @@ describe('MCMS - RBACTimelockExecuteTest', () => { ) const opcode = callExecutedExternal.body.beginParse().preloadUint(32) - const callExecutedEvent = rbactl.builder.message.out.callExecuted.decode( + const callExecutedEvent = rbactl.builder.message.out.callExecuted.load( callExecutedExternal.body, ) diff --git a/contracts/tests/mcms/RBACTimelockScheduleBatch.spec.ts b/contracts/tests/mcms/RBACTimelockScheduleBatch.spec.ts index 6f53b00f7..6c9c333ba 100644 --- a/contracts/tests/mcms/RBACTimelockScheduleBatch.spec.ts +++ b/contracts/tests/mcms/RBACTimelockScheduleBatch.spec.ts @@ -184,9 +184,7 @@ describe('MCMS - RBACTimelockScheduleBatchTest', () => { ) const opcode = result.externals[i].body.beginParse().preloadUint(32) - const callScheduled = rbactl.builder.message.out.callScheduled.decode( - result.externals[i].body, - ) + const callScheduled = rbactl.builder.message.out.callScheduled.load(result.externals[i].body) expect(opcode.toString(16)).toEqual(rbactl.opcodes.out.CallScheduled.toString(16)) expect(callScheduled.queryId).toEqual(1) diff --git a/contracts/tests/mcms/RBACTimelockUpdateDelay.spec.ts b/contracts/tests/mcms/RBACTimelockUpdateDelay.spec.ts index dd344a8f9..3c04ce73f 100644 --- a/contracts/tests/mcms/RBACTimelockUpdateDelay.spec.ts +++ b/contracts/tests/mcms/RBACTimelockUpdateDelay.spec.ts @@ -57,7 +57,7 @@ describe('MCMS - RBACTimelockUpdateDelayTest', () => { const delayChangedMsg = delayChangedTx[0].inMessage! const opcode = delayChangedMsg.body.beginParse().preloadUint(32) - const delayChangedConfirmation = rbactl.builder.message.out.minDelayChange.decode( + const delayChangedConfirmation = rbactl.builder.message.out.minDelayChange.load( delayChangedMsg.body, ) diff --git a/contracts/wrappers/ccip/FeeQuoter.ts b/contracts/wrappers/ccip/FeeQuoter.ts index 4422d4b2b..276f87694 100644 --- a/contracts/wrappers/ccip/FeeQuoter.ts +++ b/contracts/wrappers/ccip/FeeQuoter.ts @@ -10,10 +10,14 @@ import { DictionaryValue, Sender, SendMode, + Builder, + Slice, } from '@ton/core' import * as ownable2step from '../libraries/access/Ownable2Step' -import { asSnakeData } from '../../src/utils' +import { CellCodec } from '../utils' +import { asSnakeData, fromSnakeData } from '../../src/utils' +import { loadMap, loadDict, UMapToBuilder } from '../../src/utils/dict' export type FeeQuoterStorage = { ownable: ownable2step.Data @@ -93,27 +97,198 @@ export function destChainConfigToBuilder(config: DestChainConfig): TonBuilder { .storeUint(config.networkFeeUsdCents, 32) } -export const Builder = { - asStorage: (config: FeeQuoterStorage): Cell => { - return ( - beginCell() - .storeAddress(config.ownable.owner) - .storeMaybeBuilder( - config.ownable.pendingOwner - ? beginCell().storeAddress(config.ownable.pendingOwner) - : null, - ) - .storeUint(config.maxFeeJuelsPerMsg, 96) - .storeAddress(config.linkToken) - .storeUint(config.tokenPriceStalenessThreshold, 64) - .storeDict(config.usdPerToken) - .storeDict(config.premiumMultiplierWeiPerEth) - // UMap<> type - .storeDict(config.destChainConfigs) - .storeUint(64, 16) // keyLen - .endCell() - ) +export const builder = { + message: { + in: (() => { + const updatePrices: CellCodec = { + encode: (data: UpdatePrices): Builder => { + const tokenPrices = asSnakeData(data.updates.tokenPricesUpdates, encodeTokenPriceUpdate) + const gasPrices = asSnakeData(data.updates.gasPricesUpdates, encodeGasPriceUpdate) + + return beginCell() + .storeUint(Opcodes.updatePrices, 32) + .storeRef(tokenPrices) + .storeRef(gasPrices) + }, + load: (src: Slice): UpdatePrices => { + throw new Error('Not implemented') // TODO implement if needed + }, + } + const updateFeeTokens: CellCodec = { + encode: (data: UpdateFeeTokens): Builder => { + let add = Dictionary.empty(Dictionary.Keys.Address(), Dictionary.Values.BigUint(64)) + for (const [token, feeToken] of data.add) { + add.set(token, feeToken.premiumMultiplierWeiPerEth) + } + const remove = asSnakeData(data.remove, (addr) => new TonBuilder().storeAddress(addr)) + + return beginCell().storeUint(Opcodes.updateFeeTokens, 32).storeDict(add).storeRef(remove) + }, + load: (src: Slice) => { + throw new Error('Function not implemented.') // TODO implement if needed + }, + } + const updateTokenTransferFeeConfigs: CellCodec = { + encode: (data: UpdateTokenTransferFeeConfigs): Builder => { + const updatesDict = Dictionary.empty( + Dictionary.Keys.BigUint(64), + UpdateTokenTransferFeeConfigDictionaryValueType(), + ) + for (const [destChainSelector, updateTokenTransferFeeConfig] of data.updates) { + updatesDict.set(destChainSelector, updateTokenTransferFeeConfig) + } + + const updates = UMapToBuilder({ dict: updatesDict, keyLen: 64 }) + return beginCell().storeUint(Opcodes.updateTransferFeeConfigs, 32).storeBuilder(updates) + }, + load(src: Slice): UpdateTokenTransferFeeConfigs { + throw new Error('Function not implemented.') // TODO implement if needed + }, + } + const updateDestChainConfig: CellCodec = { + encode: (msg: UpdateDestChainConfig): Builder => { + return beginCell() + .storeUint(Opcodes.updateDestChainConfig, 32) + .storeUint(msg.destChainSelector, 64) + .storeBuilder(destChainConfigToBuilder(msg.destChainConfig)) + }, + load(src: Slice): UpdateDestChainConfig { + throw new Error('Function not implemented.') // TODO implement if needed + }, + } + return { + updatePrices, + updateFeeTokens, + updateTokenTransferFeeConfigs, + updateDestChainConfig, + } + })(), }, + data: (() => { + const timestampedPrice: CellCodec = { + encode: (data: TimestampedPrice): Builder => { + return beginCell().storeUint(data.value, 224).storeUint(data.timestamp, 64) + }, + load: (src: Slice): TimestampedPrice => { + return { + value: src.loadUintBig(224), + timestamp: src.loadUintBig(64), + } + }, + } + + const destChainConfig: CellCodec = { + encode: (data: DestChainConfig): Builder => { + return destChainConfigToBuilder(data) + }, + load: (src: Slice): DestChainConfig => { + return { + isEnabled: src.loadBoolean(), + maxNumberOfTokensPerMsg: src.loadUint(16), + maxDataBytes: src.loadUint(32), + maxPerMsgGasLimit: src.loadUint(32), + destGasOverhead: src.loadUint(32), + destGasPerPayloadByteBase: src.loadUint(8), + destGasPerPayloadByteHigh: src.loadUint(8), + destGasPerPayloadByteThreshold: src.loadUint(16), + destDataAvailabilityOverheadGas: src.loadUint(32), + destGasPerDataAvailabilityByte: src.loadUint(16), + destDataAvailabilityMultiplierBps: src.loadUint(16), + chainFamilySelector: src.loadUint(32), + enforceOutOfOrder: src.loadBoolean(), + defaultTokenFeeUsdCents: src.loadUint(16), + defaultTokenDestGasOverhead: src.loadUint(32), + defaultTxGasLimit: src.loadUint(32), + gasMultiplierWeiPerEth: src.loadUintBig(64), + gasPriceStalenessThreshold: src.loadUint(32), + networkFeeUsdCents: src.loadUint(32), + } + }, + } + + const tokenTransferFeeConfig: CellCodec = { + encode: (data: TokenTransferFeeConfig): Builder => { + return beginCell() + .storeBit(data.isEnabled) + .storeInt(data.minFeeUsdCents, 32) + .storeInt(data.maxFeeUsdCents, 32) + .storeInt(data.deciBps, 16) + .storeInt(data.destGasOverhead, 32) + .storeInt(data.destBytesOverhead, 32) + }, + load: (src: Slice): TokenTransferFeeConfig => { + return { + isEnabled: src.loadBoolean(), + minFeeUsdCents: src.loadUint(32), + maxFeeUsdCents: src.loadUint(32), + deciBps: src.loadUint(16), + destGasOverhead: src.loadUint(32), + destBytesOverhead: src.loadUint(32), + } + }, + } + + const contractData: CellCodec = { + encode: (data: FeeQuoterStorage): Builder => { + return beginCell() + .storeBuilder(ownable2step.builder.data.traitData.encode(data.ownable)) + .storeUint(data.maxFeeJuelsPerMsg, 96) + .storeAddress(data.linkToken) + .storeUint(data.tokenPriceStalenessThreshold, 64) + .storeDict(data.usdPerToken) + .storeDict(data.premiumMultiplierWeiPerEth) + .storeDict(data.destChainConfigs) + .storeUint(64, 16) // keyLen + }, + load: (src: Slice): FeeQuoterStorage => { + const ownable = ownable2step.builder.data.traitData.load(src) + const maxFeeJuelsPerMsg = src.loadUintBig(96) + const linkToken = src.loadAddress() + const tokenPriceStalenessThreshold = src.loadUintBig(64) + + const usdPerToken = Dictionary.loadDirect( + Dictionary.Keys.Address(), + createTimestampedPriceValue(), + src.loadRef(), + ) + + const premiumMultiplierWeiPerEth = Dictionary.loadDirect( + Dictionary.Keys.Address(), + Dictionary.Values.BigUint(64), + src.loadRef(), + ) + + const destChainConfigsRaw = Dictionary.loadDirect( + Dictionary.Keys.BigUint(64), + Dictionary.Values.Cell(), + src.loadRef(), + ) + + // Convert Cell dictionary to DestChainConfig dictionary + const destChainConfigs = Dictionary.empty() + for (const [key, configCell] of destChainConfigsRaw) { + destChainConfigs.set(key, destChainConfig.load(configCell.beginParse())) + } + + return { + ownable, + maxFeeJuelsPerMsg, + linkToken, + tokenPriceStalenessThreshold, + usdPerToken, + premiumMultiplierWeiPerEth, + destChainConfigs, + } + }, + } + + return { + timestampedPrice, + destChainConfig, + tokenTransferFeeConfig, + contractData, + } + })(), } export abstract class Params {} @@ -124,6 +299,58 @@ export abstract class Opcodes { static updateDestChainConfig = 0x20000004 } +export type TokenPriceUpdate = { + token: Address + price: bigint +} + +export type GasPriceUpdate = { + chainSelector: bigint + executionGasPrice: bigint + dataAvailabilityGasPrice: bigint +} + +export type PriceUpdates = { + tokenPricesUpdates: TokenPriceUpdate[] + gasPricesUpdates: GasPriceUpdate[] +} + +export type UpdatePrices = { + updates: PriceUpdates +} + +export type UpdateFeeTokens = { + add: Map // token address -> premium multiplier + remove: Address[] +} + +export type FeeToken = { + premiumMultiplierWeiPerEth: bigint +} + +export type UpdateTokenTransferFeeConfigs = { + updates: Map // destChainSelector -> updates +} + +export type TokenTransferFeeConfig = { + isEnabled: boolean + minFeeUsdCents: number + maxFeeUsdCents: number + deciBps: number + destGasOverhead: number + destBytesOverhead: number +} + +export type UpdateTokenTransferFeeConfig = { + add: Map // token address -> config + remove: Address[] // vector
+} + +export type UpdateDestChainConfig = { + destChainSelector: bigint + destChainConfig: DestChainConfig +} + export abstract class Errors {} export class FeeQuoter implements Contract { @@ -137,7 +364,7 @@ export class FeeQuoter implements Contract { } static createFromConfig(config: FeeQuoterStorage, code: Cell, workchain = 0) { - const data = Builder.asStorage(config) + const data = builder.data.contractData.encode(config).asCell() const init = { code, data } return new FeeQuoter(contractAddress(workchain, init), init) } @@ -154,7 +381,7 @@ export class FeeQuoter implements Contract { await provider.internal(via, { value: value, sendMode: SendMode.PAY_GAS_SEPARATELY, - body: Cell.EMPTY, + body: beginCell().endCell(), }) } @@ -163,18 +390,13 @@ export class FeeQuoter implements Contract { via: Sender, opts: { value: bigint - destChainSelector: bigint - config: DestChainConfig + msg: UpdateDestChainConfig }, ) { await provider.internal(via, { value: opts.value, sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell() - .storeUint(Opcodes.updateDestChainConfig, 32) - .storeUint(opts.destChainSelector, 64) - .storeBuilder(destChainConfigToBuilder(opts.config)) - .endCell(), + body: builder.message.in.updateDestChainConfig.encode(opts.msg).asCell(), }) } @@ -183,32 +405,13 @@ export class FeeQuoter implements Contract { via: Sender, opts: { value: bigint - gasPrices: { - chainSelector: bigint - executionGasPrice: bigint - dataAvailabilityGasPrice: bigint - }[] - tokenPrices: { token: Address; price: bigint }[] + msg: UpdatePrices }, ) { - const tokenPrices = asSnakeData(opts.tokenPrices, (config) => - new TonBuilder().storeAddress(config.token).storeInt(config.price, 224), - ) - const gasPrices = asSnakeData(opts.gasPrices, (config) => - new TonBuilder() - .storeInt(config.chainSelector, 64) - .storeInt(config.executionGasPrice, 112) - .storeInt(config.dataAvailabilityGasPrice, 112), - ) - return await provider.internal(via, { value: opts.value, sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell() - .storeUint(Opcodes.updatePrices, 32) - .storeRef(tokenPrices) - .storeRef(gasPrices) - .endCell(), + body: builder.message.in.updatePrices.encode(opts.msg).asCell(), }) } @@ -217,25 +420,84 @@ export class FeeQuoter implements Contract { via: Sender, opts: { value: bigint - add: { token: Address; premiumMultiplier: bigint }[] - remove: Address[] + msg: UpdateFeeTokens }, ) { - // token -> premiumMultiplierWeiPerEth - let add = Dictionary.empty(Dictionary.Keys.Address(), Dictionary.Values.BigUint(64)) - for (const config of opts.add) { - add.set(config.token, config.premiumMultiplier) - } - const remove = asSnakeData(opts.remove, (addr) => new TonBuilder().storeAddress(addr)) + return await provider.internal(via, { + value: opts.value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: builder.message.in.updateFeeTokens.encode(opts.msg).asCell(), + }) + } + async sendUpdateTokenTransferFeeConfigs( + provider: ContractProvider, + via: Sender, + opts: { + value: bigint + msg: UpdateTokenTransferFeeConfigs + }, + ) { return await provider.internal(via, { value: opts.value, sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell() - .storeUint(Opcodes.updateFeeTokens, 32) - .storeDict(add) - .storeRef(remove) - .endCell(), + body: builder.message.in.updateTokenTransferFeeConfigs.encode(opts.msg).asCell(), }) } } + +function encodeUpdateTokenTransferFeeConfig( + updateTokenTransferFeeConfig: UpdateTokenTransferFeeConfig, +): Cell { + let add = Dictionary.empty(Dictionary.Keys.Address(), TokenTransferFeeConfigDictionaryValueType()) + let remove = asSnakeData(updateTokenTransferFeeConfig.remove, (addr) => + new TonBuilder().storeAddress(addr), + ) + for (const [token, tokenTransferFeeConfig] of updateTokenTransferFeeConfig.add.entries()) { + add.set(token, tokenTransferFeeConfig) + } + var updateTokenTransferFeeConfigCell = beginCell().storeDict(add).storeRef(remove).endCell() + return updateTokenTransferFeeConfigCell +} + +function encodeTokenTransferFeeConfig(tokenTransferFeeConfig: TokenTransferFeeConfig): Cell { + return beginCell() + .storeBit(tokenTransferFeeConfig.isEnabled) + .storeInt(tokenTransferFeeConfig.minFeeUsdCents, 32) + .storeInt(tokenTransferFeeConfig.maxFeeUsdCents, 32) + .storeInt(tokenTransferFeeConfig.deciBps, 16) + .storeInt(tokenTransferFeeConfig.destGasOverhead, 32) + .storeInt(tokenTransferFeeConfig.destBytesOverhead, 32) + .endCell() +} + +function encodeGasPriceUpdate(gasPriceUpdate: GasPriceUpdate): TonBuilder { + return new TonBuilder() + .storeInt(gasPriceUpdate.chainSelector, 64) + .storeInt(gasPriceUpdate.executionGasPrice, 112) + .storeInt(gasPriceUpdate.dataAvailabilityGasPrice, 112) +} + +function encodeTokenPriceUpdate(tokenPriceUpdate: TokenPriceUpdate): TonBuilder { + return new TonBuilder().storeAddress(tokenPriceUpdate.token).storeInt(tokenPriceUpdate.price, 224) +} + +function UpdateTokenTransferFeeConfigDictionaryValueType(): DictionaryValue { + const serialize = (src: UpdateTokenTransferFeeConfig, builder: Builder): void => { + builder.storeBuilder(encodeUpdateTokenTransferFeeConfig(src).asBuilder()) + } + const parse = (src: Slice): UpdateTokenTransferFeeConfig => { + throw new Error('Function not implemented.') + } + return { serialize, parse } +} + +function TokenTransferFeeConfigDictionaryValueType(): DictionaryValue { + const serialize = (src: TokenTransferFeeConfig, builder: Builder): void => { + builder.storeBuilder(encodeTokenTransferFeeConfig(src).asBuilder()) + } + const parse = (src: Slice): TokenTransferFeeConfig => { + throw new Error('Function not implemented.') + } + return { serialize, parse } +} diff --git a/contracts/wrappers/ccip/Router.ts b/contracts/wrappers/ccip/Router.ts index b79ce14f7..a4a090afe 100644 --- a/contracts/wrappers/ccip/Router.ts +++ b/contracts/wrappers/ccip/Router.ts @@ -1,34 +1,26 @@ import { Address, beginCell, + Builder, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode, + Slice, } from '@ton/core' import * as ownable2step from '../libraries/access/Ownable2Step' +import { CellCodec } from '../utils' +import { asSnakeData } from '../../src/utils' -export type RouterStorage = { +export type Storage = { ownable: ownable2step.Data onRamp: Address } -export const Builder = { - /// Creates a new `AccessControl_GrantRole` message. - asStorage: (config: RouterStorage): Cell => { - return beginCell() - .storeAddress(config.ownable.owner) - .storeMaybeBuilder( - config.ownable.pendingOwner ? beginCell().storeAddress(config.ownable.pendingOwner) : null, - ) - .storeAddress(config.onRamp) - .endCell() - }, -} export abstract class Params {} export abstract class Opcodes { @@ -48,8 +40,8 @@ export class Router implements Contract { return new Router(address) } - static createFromConfig(config: RouterStorage, code: Cell, workchain = 0) { - const data = Builder.asStorage(config) + static createFromConfig(config: Storage, code: Cell, workchain = 0) { + const data = builder.data.contractData.encode(config).asCell() const init = { code, data } return new Router(contractAddress(workchain, init), init) } @@ -87,32 +79,100 @@ export class Router implements Contract { async sendCcipSend( provider: ContractProvider, via: Sender, - opts: { - value: bigint - queryID?: number - destChainSelector: bigint - receiver: Buffer - data: Cell - tokenAmounts: Cell - feeToken: Address - extraArgs: Cell - }, + opts: { value: string | bigint; body: CCIPSend }, ) { await provider.internal(via, { value: opts.value, sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell() - .storeUint(Opcodes.ccipSend, 32) - .storeUint(opts.queryID ?? 0, 64) - .storeUint(opts.destChainSelector, 64) - // CrossChainAddress TODO: assert =< 64 - .storeUint(opts.receiver.byteLength, 8) - .storeBuffer(opts.receiver, opts.receiver.byteLength) - .storeRef(opts.data) - .storeRef(opts.tokenAmounts) // TODO: pack inputs - .storeAddress(opts.feeToken) - .storeRef(opts.extraArgs) - .endCell(), + body: builder.message.in.ccipSend.encode(opts.body).asCell(), }) } } + +export type TokenAmount = { + amount: bigint + token: Address +} + +export type CCIPSend = { + queryID?: number + destChainSelector: bigint + receiver: Buffer + data: Cell + tokenAmounts: TokenAmount[] + feeToken: Address + extraArgs: Cell +} + +const tokenAmountCodec: CellCodec = { + encode: (amount: TokenAmount): Builder => { + return beginCell().storeCoins(amount.amount).storeAddress(amount.token) + }, + load: (src: Slice): TokenAmount => { + return { + amount: src.loadCoins(), + token: src.loadAddress(), + } + }, +} + +export const builder = { + data: (() => { + const contractData: CellCodec = { + encode: (config: Storage): Builder => { + return beginCell() + .storeAddress(config.ownable.owner) + .storeMaybeBuilder( + config.ownable.pendingOwner + ? beginCell().storeAddress(config.ownable.pendingOwner) + : null, + ) + .storeAddress(config.onRamp) + }, + + load: (src: Slice): Storage => { + return { + ownable: ownable2step.builder.data.traitData.load(src.loadRef().beginParse()), + onRamp: src.loadAddress(), + } + }, + } + + return { + contractData, + tokenAmountCodec, + } + })(), + message: { + in: (() => { + const ccipSend: CellCodec = { + encode: (opts: CCIPSend): Builder => { + return ( + beginCell() + .storeUint(Opcodes.ccipSend, 32) + .storeUint(opts.queryID ?? 0, 64) + .storeUint(opts.destChainSelector, 64) + // CrossChainAddress TODO: assert =< 64 + .storeUint(opts.receiver.byteLength, 8) + .storeBuffer(opts.receiver, opts.receiver.byteLength) + .storeRef(opts.data) + .storeRef( + asSnakeData(opts.tokenAmounts, (tokenAmount) => + tokenAmountCodec.encode(tokenAmount), + ), + ) // TODO: pack inputs + .storeAddress(opts.feeToken) + .storeRef(opts.extraArgs) + ) + }, + load: function (src: Slice): CCIPSend { + throw new Error('Function not implemented.') + }, + } + + return { + ccipSend, + } + })(), + }, +} diff --git a/contracts/wrappers/examples/Counter.ts b/contracts/wrappers/examples/Counter.ts index 7c49e483f..26d45326f 100644 --- a/contracts/wrappers/examples/Counter.ts +++ b/contracts/wrappers/examples/Counter.ts @@ -1,12 +1,14 @@ import { Address, beginCell, + Builder, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode, + Slice, } from '@ton/core' import { TypeAndVersion } from '../libraries/TypeAndVersion' import * as ownable2step from '../libraries/access/Ownable2Step' @@ -88,30 +90,25 @@ export const builder = { data: (() => { // Creates a new `Counter_Data` contract data cell const contractData: CellCodec = { - encode: (data: ContractData): Cell => { + encode: (data: ContractData): Builder => { let _pendingOwnerMaybe = data.ownable.pendingOwner ? beginCell().storeAddress(data.ownable.pendingOwner) : null let ownable = beginCell() .storeAddress(data.ownable.owner) .storeMaybeBuilder(_pendingOwnerMaybe) - return beginCell() - .storeUint(data.id, 32) - .storeUint(data.value, 32) - .storeBuilder(ownable) - .endCell() + return beginCell().storeUint(data.id, 32).storeUint(data.value, 32).storeBuilder(ownable) }, - decode: (cell: Cell): ContractData => { - const s = cell.beginParse() - const id = s.loadUintBig(32) - const value = s.loadUintBig(32) + load: (src: Slice): ContractData => { + const id = src.loadUintBig(32) + const value = src.loadUintBig(32) return { - id: s.loadUint(32), - value: s.loadUint(32), + id: src.loadUint(32), + value: src.loadUint(32), ownable: { // TODO: use ownable2step decoder - owner: s.loadAddress(), - pendingOwner: s.loadMaybeAddress(), + owner: src.loadAddress(), + pendingOwner: src.loadMaybeAddress(), }, } }, diff --git a/contracts/wrappers/examples/upgrades/UpgradeableCounterV1.ts b/contracts/wrappers/examples/upgrades/UpgradeableCounterV1.ts index 0540e0893..ad2b916a9 100644 --- a/contracts/wrappers/examples/upgrades/UpgradeableCounterV1.ts +++ b/contracts/wrappers/examples/upgrades/UpgradeableCounterV1.ts @@ -21,8 +21,7 @@ export type CounterConfig = { export function counterConfigToCell(config: CounterConfig): Cell { const builder = beginCell().storeUint(config.id, 32).storeUint(config.value, 32) - - ownable2step.storeOwnable2StepConfig(builder, config.ownable) + builder.storeBuilder(ownable2step.builder.data.traitData.encode(config.ownable)) return builder.endCell() } diff --git a/contracts/wrappers/examples/upgrades/UpgradeableCounterV2.ts b/contracts/wrappers/examples/upgrades/UpgradeableCounterV2.ts index 04714380d..6192a5dde 100644 --- a/contracts/wrappers/examples/upgrades/UpgradeableCounterV2.ts +++ b/contracts/wrappers/examples/upgrades/UpgradeableCounterV2.ts @@ -21,8 +21,7 @@ export type CounterConfig = { export function counterConfigToCell(config: CounterConfig): Cell { const builder = beginCell().storeUint(config.id, 32).storeUint(config.value, 32) - - ownable2step.storeOwnable2StepConfig(builder, config.ownable) + builder.storeBuilder(ownable2step.builder.data.traitData.encode(config.ownable)) return builder.endCell() } diff --git a/contracts/wrappers/lib/access/AccessControl.ts b/contracts/wrappers/lib/access/AccessControl.ts index 656ab0684..79b7b41ef 100644 --- a/contracts/wrappers/lib/access/AccessControl.ts +++ b/contracts/wrappers/lib/access/AccessControl.ts @@ -1,12 +1,14 @@ import { Address, beginCell, + Builder, Cell, Contract, ContractProvider, Dictionary, Sender, SendMode, + Slice, } from '@ton/core' import { crc32 } from 'zlib' import { CellCodec } from '../../utils' @@ -82,97 +84,93 @@ export enum Errors { export const builder = { message: { - in: { - grantRole: { - encode: (msg: GrantRole): Cell => { + in: (() => { + const grantRole: CellCodec = { + encode: (msg: GrantRole): Builder => { return beginCell() .storeUint(opcodes.in.GrantRole, 32) .storeUint(msg.queryId, 64) .storeUint(msg.role, 256) .storeAddress(msg.account) - .endCell() }, - decode: (cell: Cell): GrantRole => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): GrantRole => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - role: s.loadUintBig(256), - account: s.loadAddress(), + queryId: src.loadUintBig(64), + role: src.loadUintBig(256), + account: src.loadAddress(), } }, - } as CellCodec, + } - revokeRole: { - encode: (msg: RevokeRole): Cell => { + const revokeRole: CellCodec = { + encode: (msg: RevokeRole): Builder => { return beginCell() .storeUint(opcodes.in.RevokeRole, 32) .storeUint(msg.queryId, 64) .storeUint(msg.role, 256) .storeAddress(msg.account) - .endCell() }, - decode: (cell: Cell): RevokeRole => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): RevokeRole => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - role: s.loadUintBig(256), - account: s.loadAddress(), + queryId: src.loadUintBig(64), + role: src.loadUintBig(256), + account: src.loadAddress(), } }, - } as CellCodec, + } - renounceRole: { - encode: (msg: RenounceRole): Cell => { + const renounceRole: CellCodec = { + encode: (msg: RenounceRole): Builder => { return beginCell() .storeUint(opcodes.in.RenounceRole, 32) .storeUint(msg.queryId, 64) .storeUint(msg.role, 256) .storeAddress(msg.callerConfirmation) - .endCell() }, - decode: (cell: Cell): RenounceRole => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): RenounceRole => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - role: s.loadUintBig(256), - callerConfirmation: s.loadAddress(), + queryId: src.loadUintBig(64), + role: src.loadUintBig(256), + callerConfirmation: src.loadAddress(), } }, - } as CellCodec, - }, + } + + return { + grantRole, + revokeRole, + renounceRole, + } + })(), }, data: (() => { const roleData: CellCodec = { - encode: (data: RoleData): Cell => { + encode: (data: RoleData): Builder => { return beginCell() // break line .storeUint(data.adminRole, 256) .storeUint(data.membersLen, 64) .storeDict(data.hasRole) - .endCell() }, - decode: (cell: Cell): RoleData => { - const s = cell.beginParse() + load: (src: Slice): RoleData => { return { - adminRole: s.loadUintBig(256), - membersLen: s.loadUintBig(64), - hasRole: s.loadDict(Dictionary.Keys.Address(), Dictionary.Values.Buffer(0)), + adminRole: src.loadUintBig(256), + membersLen: src.loadUintBig(64), + hasRole: src.loadDict(Dictionary.Keys.Address(), Dictionary.Values.Buffer(0)), } }, } const contractData: CellCodec = { - encode: (data: ContractData): Cell => { + encode: (data: ContractData): Builder => { return beginCell() // break line .storeDict(data.roles, Dictionary.Keys.BigUint(256), Dictionary.Values.Cell()) - .endCell() }, - decode: (cell: Cell): ContractData => { - const s = cell.beginParse() + load: (src: Slice): ContractData => { return { - roles: s.loadDict(Dictionary.Keys.BigUint(256), Dictionary.Values.Cell()), + roles: src.loadDict(Dictionary.Keys.BigUint(256), Dictionary.Values.Cell()), } }, } @@ -218,15 +216,15 @@ export class ContractClient implements Contract { } async sendGrantRole(p: ContractProvider, via: Sender, value: bigint = 0n, body: GrantRole) { - return this.sendInternal(p, via, value, builder.message.in.grantRole.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.grantRole.encode(body).asCell()) } async sendRevokeRole(p: ContractProvider, via: Sender, value: bigint = 0n, body: RevokeRole) { - return this.sendInternal(p, via, value, builder.message.in.revokeRole.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.revokeRole.encode(body).asCell()) } async sendRenounceRole(p: ContractProvider, via: Sender, value: bigint = 0n, body: RenounceRole) { - return this.sendInternal(p, via, value, builder.message.in.renounceRole.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.renounceRole.encode(body).asCell()) } // --- Getters --- diff --git a/contracts/wrappers/libraries/access/Ownable2Step.ts b/contracts/wrappers/libraries/access/Ownable2Step.ts index 4f047f5a5..bd76dbb8b 100644 --- a/contracts/wrappers/libraries/access/Ownable2Step.ts +++ b/contracts/wrappers/libraries/access/Ownable2Step.ts @@ -7,6 +7,7 @@ import { ContractProvider, Sender, SendMode, + Slice, } from '@ton/core' import { crc32 } from 'zlib' import { CellCodec } from '../../utils' @@ -47,56 +48,62 @@ export type Data = { export const builder = { message: { - in: { + in: (() => { // Creates a new `TransferOwnership` message. - transferOwnership: { - encode: (msg: TransferOwnership): Cell => { + const transferOwnership: CellCodec = { + encode: (msg: TransferOwnership): Builder => { return beginCell() // break line .storeUint(opcodes.in.TransferOwnership, 32) .storeUint(msg.queryId, 64) .storeAddress(msg.newOwner) - .endCell() }, - decode: (cell: Cell): TransferOwnership => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): TransferOwnership => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - newOwner: s.loadAddress(), + queryId: src.loadUintBig(64), + newOwner: src.loadAddress(), } }, - }, - acceptOwnership: { - encode: (msg: AcceptOwnership): Cell => { + } + const acceptOwnership: CellCodec = { + encode: (msg: AcceptOwnership): Builder => { return beginCell() // break line .storeUint(opcodes.in.AcceptOwnership, 32) .storeUint(msg.queryId, 64) - .endCell() }, - decode: (cell: Cell): AcceptOwnership => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): AcceptOwnership => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), + queryId: src.loadUintBig(64), } }, - }, - }, + } + + return { + transferOwnership, + acceptOwnership, + } + })(), }, data: (() => { // Creates a new `Data` contract data cell const traitData: CellCodec = { - encode: (data: Data): Cell => { - let _pendingOwnerMaybe = data.pendingOwner - ? beginCell().storeAddress(data.pendingOwner) - : null - let ownable = beginCell().storeAddress(data.owner).storeMaybeBuilder(_pendingOwnerMaybe) - return beginCell().storeBuilder(ownable).endCell() + encode: (data: Data): Builder => { + var builder = beginCell() + builder.storeAddress(data.owner) + + if (data.pendingOwner) { + builder + .storeBit(1) // Store '1' to indicate the address is present + .storeAddress(data.pendingOwner) // Then store the address + } else { + builder.storeBit(0) // Store '0' to indicate the address is absent + } + return builder }, - decode: (cell: Cell): Data => { - const s = cell.beginParse() - const owner = s.loadAddress() - const pendingOwner = s.loadMaybeAddress() + load: (src: Slice): Data => { + const owner = src.loadAddress() + const pendingOwner = src.loadMaybeAddress() return { owner, pendingOwner, @@ -110,18 +117,6 @@ export const builder = { })(), } -export function storeOwnable2StepConfig(builder: Builder, config: Data) { - builder.storeAddress(config.owner) - - if (config.pendingOwner) { - builder - .storeBit(1) // Store '1' to indicate the address is present - .storeAddress(config.pendingOwner) // Then store the address - } else { - builder.storeBit(0) // Store '0' to indicate the address is absent - } -} - export class ContractClient implements Contract { constructor( readonly address: Address, @@ -146,7 +141,12 @@ export class ContractClient implements Contract { value: bigint = 0n, body: TransferOwnership, ) { - return this.sendInternal(p, via, value, builder.message.in.transferOwnership.encode(body)) + return this.sendInternal( + p, + via, + value, + builder.message.in.transferOwnership.encode(body).asCell(), + ) } async sendAcceptOwnership( @@ -155,7 +155,12 @@ export class ContractClient implements Contract { value: bigint = 0n, body: AcceptOwnership, ) { - return this.sendInternal(p, via, value, builder.message.in.acceptOwnership.encode(body)) + return this.sendInternal( + p, + via, + value, + builder.message.in.acceptOwnership.encode(body).asCell(), + ) } async getOwner(provider: ContractProvider): Promise
{ diff --git a/contracts/wrappers/mcms/CallProxy.ts b/contracts/wrappers/mcms/CallProxy.ts index 76e6a384e..3a3f4d083 100644 --- a/contracts/wrappers/mcms/CallProxy.ts +++ b/contracts/wrappers/mcms/CallProxy.ts @@ -1,12 +1,14 @@ import { Address, beginCell, + Builder, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode, + Slice, } from '@ton/core' import { crc32 } from 'zlib' import { CellCodec } from '../utils' @@ -62,14 +64,13 @@ export const builder = { data: (() => { // Creates a new `CallProxy_Data` contract data cell const contractData: CellCodec = { - encode: (data: ContractData): Cell => { - return beginCell().storeUint(data.id, 32).storeAddress(data.target).endCell() + encode: (data: ContractData): Builder => { + return beginCell().storeUint(data.id, 32).storeAddress(data.target) }, - decode: (cell: Cell): ContractData => { - const s = cell.beginParse() + load: (src: Slice): ContractData => { return { - id: s.loadUint(32), - target: s.loadAddress(), + id: src.loadUint(32), + target: src.loadAddress(), } }, } @@ -91,7 +92,7 @@ export class ContractClient implements Contract { } static newFrom(data: ContractData, code: Cell, workchain = 0) { - const init = { code, data: builder.data.contractData.encode(data) } + const init = { code, data: builder.data.contractData.encode(data).asCell() } return new ContractClient(contractAddress(workchain, init), init) } diff --git a/contracts/wrappers/mcms/MCMS.ts b/contracts/wrappers/mcms/MCMS.ts index 0fcae3b24..8d25982cd 100644 --- a/contracts/wrappers/mcms/MCMS.ts +++ b/contracts/wrappers/mcms/MCMS.ts @@ -1,6 +1,7 @@ import { Address, beginCell, + Builder, Cell, Contract, contractAddress, @@ -8,6 +9,7 @@ import { Dictionary, Sender, SendMode, + Slice, } from '@ton/core' import { crc32 } from 'zlib' import { CellCodec, sha256_32 } from '../utils' @@ -430,23 +432,21 @@ export const opcodes = { } const rootMetadata: CellCodec = { - encode: (data: RootMetadata): Cell => { + encode: (data: RootMetadata): Builder => { return beginCell() .storeInt(data.chainId, 256) .storeAddress(data.multiSig) .storeUint(data.preOpCount, 40) .storeUint(data.postOpCount, 40) .storeBit(data.overridePreviousRoot) - .endCell() }, - decode: (cell: Cell): RootMetadata => { - const s = cell.beginParse() + load: (src: Slice): RootMetadata => { return { - chainId: s.loadIntBig(256), - multiSig: s.loadAddress(), - preOpCount: s.loadUintBig(40), - postOpCount: s.loadUintBig(40), - overridePreviousRoot: s.loadBoolean(), + chainId: src.loadIntBig(256), + multiSig: src.loadAddress(), + preOpCount: src.loadUintBig(40), + postOpCount: src.loadUintBig(40), + overridePreviousRoot: src.loadBoolean(), } }, } @@ -456,71 +456,65 @@ export const builder = { in: { // Creates a new `MCMS_TopUp` message. topUp: { - encode: (msg: TopUp): Cell => { + encode: (msg: TopUp): Builder => { return beginCell() // break line .storeUint(opcodes.in.TopUp, 32) .storeUint(msg.queryId, 64) - .endCell() }, - decode: (cell: Cell): TopUp => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): TopUp => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), + queryId: src.loadUintBig(64), } }, }, // Creates a new `MCMS_SetRoot` message. setRoot: { - encode: (msg: SetRoot): Cell => { + encode: (msg: SetRoot): Builder => { return beginCell() .storeUint(opcodes.in.SetRoot, 32) .storeUint(msg.queryId, 64) .storeUint(msg.root, 256) .storeUint(msg.validUntil, 32) - .storeBuilder(rootMetadata.encode(msg.metadata).asBuilder()) + .storeBuilder(rootMetadata.encode(msg.metadata)) .storeRef(msg.metadataProof) .storeRef(msg.signatures) .storeUint(msg.opFinalizationTimeout, 32) - .endCell() }, - decode: (cell: Cell): SetRoot => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): SetRoot => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - root: s.loadUintBig(256), - validUntil: s.loadUintBig(32), - metadata: s.loadRef().beginParse() as unknown as RootMetadata, // TODO: decode metadata properly - metadataProof: s.loadRef(), - signatures: s.loadRef(), - opFinalizationTimeout: s.loadUintBig(32), + queryId: src.loadUintBig(64), + root: src.loadUintBig(256), + validUntil: src.loadUintBig(32), + metadata: src.loadRef().beginParse() as unknown as RootMetadata, // TODO: decode metadata properly + metadataProof: src.loadRef(), + signatures: src.loadRef(), + opFinalizationTimeout: src.loadUintBig(32), } }, }, // Creates a new `MCMS_Execute` message. execute: { - encode: (msg: Execute): Cell => { + encode: (msg: Execute): Builder => { return beginCell() .storeUint(opcodes.in.Execute, 32) .storeUint(msg.queryId, 64) .storeRef(msg.op) .storeRef(asSnakeData(msg.proof, (v) => beginCell().storeUint(v, 256))) - .endCell() }, - decode: (cell: Cell): Execute => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): Execute => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - op: s.loadRef(), - proof: fromSnakeData(s.loadRef(), (a) => a.loadUintBig(256)), + queryId: src.loadUintBig(64), + op: src.loadRef(), + proof: fromSnakeData(src.loadRef(), (a) => a.loadUintBig(256)), } }, }, // Creates a new `MCMS_SetConfig` message. setConfig: { - encode: (msg: SetConfig): Cell => { + encode: (msg: SetConfig): Builder => { return beginCell() .storeUint(opcodes.in.SetConfig, 32) .storeUint(msg.queryId, 64) @@ -533,35 +527,33 @@ export const builder = { loadMap(Dictionary.Keys.Uint(8), Dictionary.Values.Uint(8), msg.groupParents), ) .storeBit(msg.clearRoot) - .endCell() }, - decode: (cell: Cell): SetConfig => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): SetConfig => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - signerKeys: fromSnakeData(s.loadRef(), (a) => a.loadUintBig(256)), - signerGroups: fromSnakeData(s.loadRef(), (g) => g.loadUint(8)), + queryId: src.loadUintBig(64), + signerKeys: fromSnakeData(src.loadRef(), (a) => a.loadUintBig(256)), + signerGroups: fromSnakeData(src.loadRef(), (g) => g.loadUint(8)), groupQuorums: loadDict( Dictionary.loadDirect( Dictionary.Keys.Uint(8), Dictionary.Values.Uint(8), - s.loadRef(), + src.loadRef(), ), ), groupParents: loadDict( Dictionary.loadDirect( Dictionary.Keys.Uint(8), Dictionary.Values.Uint(8), - s.loadRef(), + src.loadRef(), ), ), - clearRoot: s.loadBoolean(), + clearRoot: src.loadBoolean(), } }, }, submitErrorReport: { - encode: (msg: SubmitErrorReport): Cell => { + encode: (msg: SubmitErrorReport): Builder => { return beginCell() .storeUint(opcodes.in.SubmitErrorReport, 32) .storeUint(msg.queryId, 64) @@ -570,35 +562,31 @@ export const builder = { .storeUint(msg.opTxHash, 256) .storeUint(msg.errorTxHash, 256) .storeUint(msg.errorCode, 32) - .endCell() }, - decode: (cell: Cell): SubmitErrorReport => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): SubmitErrorReport => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - op: s.loadRef(), - proof: fromSnakeData(s.loadRef(), (a) => a.loadUintBig(256)), - opTxHash: s.loadUintBig(256), - errorTxHash: s.loadUintBig(256), - errorCode: s.loadUint(32), + queryId: src.loadUintBig(64), + op: src.loadRef(), + proof: fromSnakeData(src.loadRef(), (a) => a.loadUintBig(256)), + opTxHash: src.loadUintBig(256), + errorTxHash: src.loadUintBig(256), + errorCode: src.loadUint(32), } }, }, transferOracleRole: { - encode: (msg: TransferOracleRole): Cell => { + encode: (msg: TransferOracleRole): Builder => { return beginCell() .storeUint(opcodes.in.TransferOracleRole, 32) .storeUint(msg.queryId, 64) .storeAddress(msg.newOracle) - .endCell() }, - decode: (cell: Cell): TransferOracleRole => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): TransferOracleRole => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - newOracle: s.loadAddress(), + queryId: src.loadUintBig(64), + newOracle: src.loadAddress(), } }, }, @@ -606,7 +594,7 @@ export const builder = { }, data: (() => { const config: CellCodec = { - encode: (data: Config): Cell => { + encode: (data: Config): Builder => { const signers = loadMap( Dictionary.Keys.Uint(8), Dictionary.Values.Buffer(LEN_SIGNER_BYTES), @@ -626,107 +614,105 @@ export const builder = { .storeDict(signers, Dictionary.Keys.Uint(8), Dictionary.Values.Buffer(LEN_SIGNER_BYTES)) .storeDict(groupQuorums, Dictionary.Keys.Uint(8), Dictionary.Values.Uint(8)) .storeDict(groupParents, Dictionary.Keys.Uint(8), Dictionary.Values.Uint(8)) - .endCell() }, - decode: (cell: Cell): Config => { - const s = cell.beginParse() + load: (src: Slice): Config => { return { signers: loadDict( Dictionary.loadDirect( Dictionary.Keys.Uint(8), Dictionary.Values.Buffer(LEN_SIGNER_BYTES), - s.loadRef(), + src.loadRef(), ), ), groupQuorums: loadDict( - Dictionary.loadDirect(Dictionary.Keys.Uint(8), Dictionary.Values.Uint(8), s.loadRef()), + Dictionary.loadDirect( + Dictionary.Keys.Uint(8), + Dictionary.Values.Uint(8), + src.loadRef(), + ), ), groupParents: loadDict( - Dictionary.loadDirect(Dictionary.Keys.Uint(8), Dictionary.Values.Uint(8), s.loadRef()), + Dictionary.loadDirect( + Dictionary.Keys.Uint(8), + Dictionary.Values.Uint(8), + src.loadRef(), + ), ), } }, } const opPendingInfo: CellCodec = { - encode: (data: OpPendingInfo): Cell => { + encode: (data: OpPendingInfo): Builder => { return beginCell() .storeUint(data.validAfter, 32) .storeUint(data.opFinalizationTimeout, 32) .storeAddress(data.opPendingReceiver) .storeUint(data.opPendingBodyTruncated, 256) - .endCell() }, - decode: (cell: Cell): OpPendingInfo => { - const s = cell.beginParse() + load: (src: Slice): OpPendingInfo => { return { - validAfter: s.loadUintBig(32), - opFinalizationTimeout: s.loadUintBig(32), - opPendingReceiver: s.loadAddress(), - opPendingBodyTruncated: s.loadUintBig(256), + validAfter: src.loadUintBig(32), + opFinalizationTimeout: src.loadUintBig(32), + opPendingReceiver: src.loadAddress(), + opPendingBodyTruncated: src.loadUintBig(256), } }, } const expiringRootAndOpCount: CellCodec = { - encode: (data: ExpiringRootAndOpCount): Cell => { + encode: (data: ExpiringRootAndOpCount): Builder => { return beginCell() .storeUint(data.root, 256) .storeUint(data.validUntil, 32) .storeUint(data.opCount, 40) .storeRef(opPendingInfo.encode(data.opPendingInfo)) - .endCell() }, - decode: (cell: Cell): ExpiringRootAndOpCount => { - const s = cell.beginParse() + load: (src: Slice): ExpiringRootAndOpCount => { return { - root: s.loadUintBig(256), - validUntil: s.loadUintBig(32), - opCount: s.loadUintBig(40), - opPendingInfo: opPendingInfo.decode(s.loadRef()), + root: src.loadUintBig(256), + validUntil: src.loadUintBig(32), + opCount: src.loadUintBig(40), + opPendingInfo: opPendingInfo.load(src.loadRef().beginParse()), } }, } /// Information about the current root, extracted into a separate struct (wrapped in a cell). const rootInfo: CellCodec = { - encode: (data: RootInfo): Cell => { + encode: (data: RootInfo): Builder => { return beginCell() - .storeBuilder(expiringRootAndOpCount.encode(data.expiringRootAndOpCount).asBuilder()) - .storeBuilder(rootMetadata.encode(data.rootMetadata).asBuilder()) - .endCell() + .storeBuilder(expiringRootAndOpCount.encode(data.expiringRootAndOpCount)) + .storeBuilder(rootMetadata.encode(data.rootMetadata)) }, - decode: (cell: Cell): RootInfo => { - const s = cell.beginParse() + load: (src: Slice): RootInfo => { return { - expiringRootAndOpCount: expiringRootAndOpCount.decode(s.asCell()), - rootMetadata: rootMetadata.decode(s.asCell()), + expiringRootAndOpCount: expiringRootAndOpCount.load(src), + rootMetadata: rootMetadata.load(src), } }, } // Creates a new `Signer` data cell const signer: CellCodec = { - encode: (signer: Signer): Cell => { + encode: (signer: Signer): Builder => { return beginCell() .storeUint(signer.key, 256) .storeUint(signer.index, 8) .storeUint(signer.group, 8) - .endCell() }, - decode: (cell: Cell): Signer => { - const s = cell.beginParse() + load: (src: Slice): Signer => { return { - key: s.loadUintBig(256), - index: s.loadUint(8), - group: s.loadUint(8), + key: src.loadUintBig(256), + index: src.loadUint(8), + group: src.loadUint(8), } }, } // Creates a new `MCMS_Op` data cell const op: CellCodec = { - encode: (op: Op): Cell => { + encode: (op: Op): Builder => { return beginCell() .storeInt(op.chainId, 256) .storeAddress(op.multiSig) @@ -734,41 +720,34 @@ export const builder = { .storeAddress(op.to) .storeCoins(op.value) .storeRef(op.data) - .endCell() }, - decode: (cell: Cell): Op => { - const s = cell.beginParse() + load: (src: Slice): Op => { return { - chainId: s.loadIntBig(256), - multiSig: s.loadAddress(), - nonce: s.loadUintBig(40), - to: s.loadAddress(), - value: s.loadCoins(), - data: s.loadRef(), + chainId: src.loadIntBig(256), + multiSig: src.loadAddress(), + nonce: src.loadUintBig(40), + to: src.loadAddress(), + value: src.loadCoins(), + data: src.loadRef(), } }, } const signature: CellCodec = { - encode: (data: Signature): Cell => { - return beginCell() - .storeUint(data.r, 256) - .storeUint(data.s, 256) - .storeUint(data.signer, 256) - .endCell() + encode: (data: Signature): Builder => { + return beginCell().storeUint(data.r, 256).storeUint(data.s, 256).storeUint(data.signer, 256) }, - decode: (cell: Cell): Signature => { - const s = cell.beginParse() + load: (src: Slice): Signature => { return { - r: s.loadUintBig(256), - s: s.loadUintBig(256), - signer: s.loadUintBig(256), + r: src.loadUintBig(256), + s: src.loadUintBig(256), + signer: src.loadUintBig(256), } }, } // Creates a new `MCMS_Data` contract data cell const contractData: CellCodec = { - encode: (data: ContractData): Cell => { + encode: (data: ContractData): Builder => { let _pendingOwnerMaybe = data.ownable.pendingOwner ? beginCell().storeAddress(data.ownable.pendingOwner) : null @@ -795,33 +774,31 @@ export const builder = { Dictionary.Values.Bool(), ) .storeRef(rootInfo.encode(data.rootInfo)) - .endCell() }, - decode: (cell: Cell): ContractData => { - const s = cell.beginParse() + load: (src: Slice): ContractData => { return { - id: s.loadUint(32), + id: src.loadUint(32), ownable: { - owner: s.loadAddress(), - pendingOwner: s.loadAddress(), + owner: src.loadAddress(), + pendingOwner: src.loadAddress(), }, - oracle: s.loadAddress(), + oracle: src.loadAddress(), signers: loadDict( Dictionary.loadDirect( Dictionary.Keys.BigUint(256), Dictionary.Values.Buffer(LEN_SIGNER_BYTES), - s.loadRef(), + src.loadRef(), ), ), - config: config.decode(s.loadRef()), + config: config.load(src.loadRef().beginParse()), seenSignedHashes: loadDict( Dictionary.loadDirect( Dictionary.Keys.BigUint(256), Dictionary.Values.Bool(), - s.loadRef(), + src.loadRef(), ), ), - rootInfo: rootInfo.decode(s.loadRef()), + rootInfo: rootInfo.load(src.loadRef().beginParse()), } }, } @@ -889,7 +866,7 @@ export class ContractClient implements Contract { } static newFrom(data: ContractData, code: Cell, workchain = 0) { - const init = { code, data: builder.data.contractData.encode(data) } + const init = { code, data: builder.data.contractData.encode(data).endCell() } return new ContractClient(contractAddress(workchain, init), init) } @@ -898,19 +875,19 @@ export class ContractClient implements Contract { } async sendTopUp(p: ContractProvider, via: Sender, value: bigint = 0n, body: TopUp) { - return this.sendInternal(p, via, value, builder.message.in.topUp.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.topUp.encode(body).endCell()) } async sendSetRoot(p: ContractProvider, via: Sender, value: bigint = 0n, body: SetRoot) { - return this.sendInternal(p, via, value, builder.message.in.setRoot.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.setRoot.encode(body).endCell()) } async sendExecute(p: ContractProvider, via: Sender, value: bigint = 0n, body: Execute) { - return this.sendInternal(p, via, value, builder.message.in.execute.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.execute.encode(body).endCell()) } async sendSetConfig(p: ContractProvider, via: Sender, value: bigint = 0n, body: SetConfig) { - return this.sendInternal(p, via, value, builder.message.in.setConfig.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.setConfig.encode(body).endCell()) } // --- Getters --- diff --git a/contracts/wrappers/mcms/RBACTimelock.ts b/contracts/wrappers/mcms/RBACTimelock.ts index 9af1367b0..b24c53f02 100644 --- a/contracts/wrappers/mcms/RBACTimelock.ts +++ b/contracts/wrappers/mcms/RBACTimelock.ts @@ -1,6 +1,7 @@ import { Address, beginCell, + Builder, Cell, Contract, contractAddress, @@ -8,6 +9,7 @@ import { Dictionary, Sender, SendMode, + Slice, } from '@ton/core' import { crc32 } from 'zlib' import { CellCodec, sha256_32 } from '../utils' @@ -248,52 +250,51 @@ export const opcodes = { export const builder = { message: { - init: { - encode: (msg: Init): Cell => { - return beginCell() - .storeUint(opcodes.in.Init, 32) - .storeUint(msg.queryId, 64) - .storeUint(msg.minDelay, 64) - .storeAddress(msg.admin) - .storeRef(msg.proposers) - .storeRef(msg.executors) - .storeRef(msg.cancellers) - .storeRef(msg.bypassers) - .endCell() - }, - decode: (cell: Cell): Init => { - const s = cell.beginParse() - s.skip(32) // skip opcode - return { - queryId: s.loadUintBig(64), - minDelay: s.loadUintBig(64), - admin: s.loadAddress(), - proposers: s.loadRef(), - executors: s.loadRef(), - cancellers: s.loadRef(), - bypassers: s.loadRef(), - } - }, - } as CellCodec, - in: { - topUp: { - encode: (msg: TopUp): Cell => { + init: (() => { + const init: CellCodec = { + encode: (msg: Init): Builder => { + return beginCell() + .storeUint(opcodes.in.Init, 32) + .storeUint(msg.queryId, 64) + .storeUint(msg.minDelay, 64) + .storeAddress(msg.admin) + .storeRef(msg.proposers) + .storeRef(msg.executors) + .storeRef(msg.cancellers) + .storeRef(msg.bypassers) + }, + load: (src: Slice): Init => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + minDelay: src.loadUintBig(64), + admin: src.loadAddress(), + proposers: src.loadRef(), + executors: src.loadRef(), + cancellers: src.loadRef(), + bypassers: src.loadRef(), + } + }, + } + return init + })(), + in: (() => { + const topUp: CellCodec = { + encode: (msg: TopUp): Builder => { return beginCell() // break line .storeUint(opcodes.in.TopUp, 32) .storeUint(msg.queryId, 64) - .endCell() }, - decode: (cell: Cell): TopUp => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): TopUp => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), + queryId: src.loadUintBig(64), } }, - } as CellCodec, + } - scheduleBatch: { - encode: (msg: ScheduleBatch): Cell => { + const scheduleBatch: CellCodec = { + encode: (msg: ScheduleBatch): Builder => { return beginCell() .storeUint(opcodes.in.ScheduleBatch, 32) .storeUint(msg.queryId, 64) @@ -301,137 +302,134 @@ export const builder = { .storeUint(msg.predecessor, 256) .storeUint(msg.salt, 256) .storeUint(msg.delay, 64) - .endCell() }, - decode: (cell: Cell): ScheduleBatch => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): ScheduleBatch => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - calls: s.loadRef(), - predecessor: s.loadUintBig(256), - salt: s.loadUintBig(256), - delay: s.loadUintBig(64), + queryId: src.loadUintBig(64), + calls: src.loadRef(), + predecessor: src.loadUintBig(256), + salt: src.loadUintBig(256), + delay: src.loadUintBig(64), } }, - } as CellCodec, + } - cancel: { - encode: (msg: Cancel): Cell => { + const cancel: CellCodec = { + encode: (msg: Cancel): Builder => { return beginCell() .storeUint(opcodes.in.Cancel, 32) .storeUint(msg.queryId, 64) .storeUint(msg.id, 256) - .endCell() }, - decode: (cell: Cell): Cancel => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): Cancel => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - id: s.loadUintBig(256), + queryId: src.loadUintBig(64), + id: src.loadUintBig(256), } }, - } as CellCodec, + } - executeBatch: { - encode: (msg: ExecuteBatch): Cell => { + const executeBatch: CellCodec = { + encode: (msg: ExecuteBatch): Builder => { return beginCell() .storeUint(opcodes.in.ExecuteBatch, 32) .storeUint(msg.queryId, 64) .storeRef(msg.calls) .storeUint(msg.predecessor, 256) .storeUint(msg.salt, 256) - .endCell() }, - decode: (cell: Cell): ExecuteBatch => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): ExecuteBatch => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - calls: s.loadRef(), - predecessor: s.loadUintBig(256), - salt: s.loadUintBig(256), + queryId: src.loadUintBig(64), + calls: src.loadRef(), + predecessor: src.loadUintBig(256), + salt: src.loadUintBig(256), } }, - } as CellCodec, + } - updateDelay: { - encode: (msg: UpdateDelay): Cell => { + const updateDelay: CellCodec = { + encode: (msg: UpdateDelay): Builder => { return beginCell() .storeUint(opcodes.in.UpdateDelay, 32) .storeUint(msg.queryId, 64) .storeUint(msg.newDelay, 64) - .endCell() }, - decode: (cell: Cell): UpdateDelay => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): UpdateDelay => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), + queryId: src.loadUintBig(64), newDelay: -1, // TODO: decode delay properly (number vs bigint mismatch) - // newDelay: s.loadUintBig(64), + // newDelay: src.loadUintBig(64), } }, - } as CellCodec, + } - blockFunctionSelector: { - encode: (msg: BlockFunctionSelector): Cell => { + const blockFunctionSelector: CellCodec = { + encode: (msg: BlockFunctionSelector): Builder => { return beginCell() .storeUint(opcodes.in.BlockFunctionSelector, 32) .storeUint(msg.queryId, 64) .storeUint(msg.selector, 32) - .endCell() }, - decode: (cell: Cell): BlockFunctionSelector => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): BlockFunctionSelector => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - selector: s.loadUint(32), + queryId: src.loadUintBig(64), + selector: src.loadUint(32), } }, - } as CellCodec, + } - unblockFunctionSelector: { - encode: (msg: UnblockFunctionSelector): Cell => { + const unblockFunctionSelector: CellCodec = { + encode: (msg: UnblockFunctionSelector): Builder => { return beginCell() .storeUint(opcodes.in.UnblockFunctionSelector, 32) .storeUint(msg.queryId, 64) .storeUint(msg.selector, 32) - .endCell() }, - decode: (cell: Cell): UnblockFunctionSelector => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): UnblockFunctionSelector => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - selector: s.loadUint(32), + queryId: src.loadUintBig(64), + selector: src.loadUint(32), } }, - } as CellCodec, + } - bypasserExecuteBatch: { - encode: (msg: BypasserExecuteBatch): Cell => { + const bypasserExecuteBatch: CellCodec = { + encode: (msg: BypasserExecuteBatch): Builder => { return beginCell() .storeUint(opcodes.in.BypasserExecuteBatch, 32) .storeUint(msg.queryId, 64) .storeRef(msg.calls) - .endCell() }, - decode: (cell: Cell): BypasserExecuteBatch => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): BypasserExecuteBatch => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - calls: s.loadRef(), + queryId: src.loadUintBig(64), + calls: src.loadRef(), } }, - } as CellCodec, - }, - out: { - callScheduled: { - encode: (event: CallScheduled): Cell => { + } + + return { + topUp, + scheduleBatch, + cancel, + executeBatch, + updateDelay, + blockFunctionSelector, + unblockFunctionSelector, + bypasserExecuteBatch, + } + })(), + out: (() => { + const callScheduled: CellCodec = { + encode: (event: CallScheduled): Builder => { return beginCell() .storeUint(opcodes.out.CallScheduled, 32) .storeUint(event.queryId, 64) @@ -441,24 +439,22 @@ export const builder = { .storeUint(event.predecessor, 256) .storeUint(event.salt, 256) .storeUint(event.delay, 64) - .endCell() }, - decode: (cell: Cell): CallScheduled => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): CallScheduled => { + src.skip(32) // skip opcode return { - queryId: s.loadUint(64), - id: s.loadUintBig(256), - index: s.loadUint(64), - call: s.loadRef(), - predecessor: s.loadUintBig(256), - salt: s.loadUintBig(256), - delay: s.loadUint(64), + queryId: src.loadUint(64), + id: src.loadUintBig(256), + index: src.loadUint(64), + call: src.loadRef(), + predecessor: src.loadUintBig(256), + salt: src.loadUintBig(256), + delay: src.loadUint(64), } }, - } as CellCodec, - callExecuted: { - encode: (event: CallExecuted): Cell => { + } + const callExecuted: CellCodec = { + encode: (event: CallExecuted): Builder => { return beginCell() .storeUint(opcodes.out.CallExecuted, 32) .storeUint(event.queryId, 64) @@ -467,23 +463,21 @@ export const builder = { .storeAddress(event.target) .storeCoins(event.value) .storeRef(event.data) - .endCell() }, - decode: (cell: Cell): CallExecuted => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): CallExecuted => { + src.skip(32) // skip opcode return { - queryId: s.loadUint(64), - id: s.loadUintBig(256), - index: s.loadUint(64), - target: s.loadAddress(), - value: s.loadCoins(), - data: s.loadRef(), + queryId: src.loadUint(64), + id: src.loadUintBig(256), + index: src.loadUint(64), + target: src.loadAddress(), + value: src.loadCoins(), + data: src.loadRef(), } }, - } as CellCodec, - bypasserCallExecuted: { - encode: (event: BypasserCallExecuted): Cell => { + } + const bypasserCallExecuted: CellCodec = { + encode: (event: BypasserCallExecuted): Builder => { return beginCell() .storeUint(opcodes.out.BypasserCallExecuted, 32) .storeUint(event.queryId, 64) @@ -491,95 +485,95 @@ export const builder = { .storeAddress(event.target) .storeCoins(event.value) .storeRef(event.data) - .endCell() }, - decode: (cell: Cell): BypasserCallExecuted => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): BypasserCallExecuted => { + src.skip(32) // skip opcode return { - queryId: s.loadUint(64), - index: s.loadUint(64), - target: s.loadAddress(), - value: s.loadCoins(), - data: s.loadRef(), + queryId: src.loadUint(64), + index: src.loadUint(64), + target: src.loadAddress(), + value: src.loadCoins(), + data: src.loadRef(), } }, - } as CellCodec, - canceled: { - encode: (event: Canceled): Cell => { + } + const canceled: CellCodec = { + encode: (event: Canceled): Builder => { return beginCell() .storeUint(opcodes.out.Canceled, 32) .storeUint(event.queryId, 64) .storeUint(event.id, 256) - .endCell() }, - decode: (cell: Cell): Canceled => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): Canceled => { + src.skip(32) // skip opcode return { - queryId: s.loadUint(64), - id: s.loadUintBig(256), + queryId: src.loadUint(64), + id: src.loadUintBig(256), } }, - } as CellCodec, - minDelayChange: { - encode: (event: MinDelayChange): Cell => { + } + const minDelayChange: CellCodec = { + encode: (event: MinDelayChange): Builder => { return beginCell() .storeUint(opcodes.out.MinDelayChange, 32) .storeUint(event.queryId, 64) .storeUint(event.oldDelay, 64) .storeUint(event.newDelay, 64) - .endCell() }, - decode: (cell: Cell): MinDelayChange => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): MinDelayChange => { + src.skip(32) // skip opcode return { - queryId: s.loadUint(64), - oldDelay: s.loadUint(64), - newDelay: s.loadUint(64), + queryId: src.loadUint(64), + oldDelay: src.loadUint(64), + newDelay: src.loadUint(64), } }, - } as CellCodec, - functionSelectorBlocked: { - encode: (event: FunctionSelectorBlocked): Cell => { + } + const functionSelectorBlocked: CellCodec = { + encode: (event: FunctionSelectorBlocked): Builder => { return beginCell() .storeUint(opcodes.out.FunctionSelectorBlocked, 32) .storeUint(event.queryId, 64) .storeUint(event.selector, 32) - .endCell() }, - decode: (cell: Cell): FunctionSelectorBlocked => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): FunctionSelectorBlocked => { + src.skip(32) // skip opcode return { - queryId: s.loadUint(64), - selector: s.loadUint(32), + queryId: src.loadUint(64), + selector: src.loadUint(32), } }, - } as CellCodec, - functionSelectorUnblocked: { - encode: (event: FunctionSelectorUnblocked): Cell => { + } + const functionSelectorUnblocked: CellCodec = { + encode: (event: FunctionSelectorUnblocked): Builder => { return beginCell() .storeUint(opcodes.out.FunctionSelectorUnblocked, 32) .storeUint(event.queryId, 64) .storeUint(event.selector, 32) - .endCell() }, - decode: (cell: Cell): FunctionSelectorUnblocked => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): FunctionSelectorUnblocked => { + src.skip(32) // skip opcode return { - queryId: s.loadUint(64), - selector: s.loadUint(32), + queryId: src.loadUint(64), + selector: src.loadUint(32), } }, - } as CellCodec, - }, + } + + return { + callScheduled, + callExecuted, + bypasserCallExecuted, + canceled, + minDelayChange, + functionSelectorBlocked, + functionSelectorUnblocked, + } + })(), }, - data: { - contractData: { - encode: (data: ContractData): Cell => { + data: (() => { + const contractData: CellCodec = { + encode: (data: ContractData): Builder => { return beginCell() .storeUint(data.id, 32) .storeUint(data.minDelay, 64) @@ -590,49 +584,43 @@ export const builder = { Dictionary.empty(Dictionary.Keys.Uint(32), Dictionary.Values.Buffer(0)), ) .storeRef(data.rbac) - .endCell() }, - decode: (cell: Cell): ContractData => { + load: (src: Slice): ContractData => { throw new Error('not implemented') }, - } as CellCodec, - - call: { - encode: (call: Call): Cell => { - return beginCell() - .storeAddress(call.target) - .storeCoins(call.value) - .storeRef(call.data) - .endCell() + } + const call: CellCodec = { + encode: (call: Call): Builder => { + return beginCell().storeAddress(call.target).storeCoins(call.value).storeRef(call.data) }, - decode: (cell: Cell): Call => { - const stack = cell.beginParse() + load: (src: Slice): Call => { return { - target: stack.loadAddress(), - value: stack.loadCoins(), - data: stack.loadRef(), + target: src.loadAddress(), + value: src.loadCoins(), + data: src.loadRef(), } }, - } as CellCodec, + } - operationBatch: { - encode: (op: OperationBatch): Cell => { - return beginCell() - .storeRef(op.calls) - .storeUint(op.predecessor, 256) - .storeUint(op.salt, 256) - .endCell() + const operationBatch: CellCodec = { + encode: (op: OperationBatch): Builder => { + return beginCell().storeRef(op.calls).storeUint(op.predecessor, 256).storeUint(op.salt, 256) }, - decode: (cell: Cell): OperationBatch => { - const s = cell.beginParse() + load: (src: Slice): OperationBatch => { return { - calls: s.loadRef(), - predecessor: s.loadUintBig(256), - salt: s.loadUintBig(256), + calls: src.loadRef(), + predecessor: src.loadUintBig(256), + salt: src.loadUintBig(256), } }, - } as CellCodec, - }, + } + + return { + contractData, + call, + operationBatch, + } + })(), } // Compute the role ID for a given role name as keccak256() @@ -679,7 +667,7 @@ export class ContractClient implements Contract { } static newFrom(data: ContractData, code: Cell, workchain = 0) { - const init = { code, data: builder.data.contractData.encode(data) } + const init = { code, data: builder.data.contractData.encode(data).asCell() } return new ContractClient(contractAddress(workchain, init), init) } @@ -692,11 +680,11 @@ export class ContractClient implements Contract { } async sendInit(provider: ContractProvider, via: Sender, value: bigint, body: Init) { - return this.sendInternal(provider, via, value, builder.message.init.encode(body)) + return this.sendInternal(provider, via, value, builder.message.init.encode(body).asCell()) } async sendTopUp(provider: ContractProvider, via: Sender, value: bigint = 0n, body: TopUp) { - return this.sendInternal(provider, via, value, builder.message.in.topUp.encode(body)) + return this.sendInternal(provider, via, value, builder.message.in.topUp.encode(body).asCell()) } async sendScheduleBatch( @@ -705,19 +693,19 @@ export class ContractClient implements Contract { value: bigint = 0n, body: ScheduleBatch, ) { - return this.sendInternal(p, via, value, builder.message.in.scheduleBatch.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.scheduleBatch.encode(body).asCell()) } async sendCancel(p: ContractProvider, via: Sender, value: bigint = 0n, body: Cancel) { - return this.sendInternal(p, via, value, builder.message.in.cancel.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.cancel.encode(body).asCell()) } async sendExecuteBatch(p: ContractProvider, via: Sender, value: bigint = 0n, body: ExecuteBatch) { - return this.sendInternal(p, via, value, builder.message.in.executeBatch.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.executeBatch.encode(body).asCell()) } async sendUpdateDelay(p: ContractProvider, via: Sender, value: bigint = 0n, body: UpdateDelay) { - return this.sendInternal(p, via, value, builder.message.in.updateDelay.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.updateDelay.encode(body).asCell()) } async sendBlockFunctionSelector( @@ -726,7 +714,12 @@ export class ContractClient implements Contract { value: bigint = 0n, body: BlockFunctionSelector, ) { - return this.sendInternal(p, via, value, builder.message.in.blockFunctionSelector.encode(body)) + return this.sendInternal( + p, + via, + value, + builder.message.in.blockFunctionSelector.encode(body).asCell(), + ) } async sendUnblockFunctionSelector( @@ -735,7 +728,12 @@ export class ContractClient implements Contract { value: bigint = 0n, body: UnblockFunctionSelector, ) { - return this.sendInternal(p, via, value, builder.message.in.unblockFunctionSelector.encode(body)) + return this.sendInternal( + p, + via, + value, + builder.message.in.unblockFunctionSelector.encode(body).asCell(), + ) } async sendBypasserExecuteBatch( @@ -744,7 +742,12 @@ export class ContractClient implements Contract { value: bigint = 0n, body: BypasserExecuteBatch, ) { - return this.sendInternal(p, via, value, builder.message.in.bypasserExecuteBatch.encode(body)) + return this.sendInternal( + p, + via, + value, + builder.message.in.bypasserExecuteBatch.encode(body).asCell(), + ) } // --- Getters --- diff --git a/contracts/wrappers/utils.ts b/contracts/wrappers/utils.ts index a27acff5c..70e26211b 100644 --- a/contracts/wrappers/utils.ts +++ b/contracts/wrappers/utils.ts @@ -1,4 +1,4 @@ -import { Cell } from '@ton/core' +import { Builder, Slice } from '@ton/core' import { createHash } from 'crypto' // Helper function to compute a 32-bit SHA-256 hash of a string (e.g., Tolk's stringSha256_32) @@ -9,6 +9,6 @@ export const sha256_32 = (input: string): bigint => { } export interface CellCodec { - encode: (data: T) => Cell - decode: (cell: Cell) => T + encode: (data: T) => Builder + load: (src: Slice) => T } From 4cbf9c07ce1b266fd2db2e79ba68b64167df4af6 Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Wed, 10 Sep 2025 15:15:46 -0300 Subject: [PATCH 2/8] fix: missing updates --- contracts/wrappers/examples/Counter.ts | 55 +++++++++++--------------- contracts/wrappers/mcms/CallProxy.ts | 19 +++++---- 2 files changed, 33 insertions(+), 41 deletions(-) diff --git a/contracts/wrappers/examples/Counter.ts b/contracts/wrappers/examples/Counter.ts index 26d45326f..1c2010991 100644 --- a/contracts/wrappers/examples/Counter.ts +++ b/contracts/wrappers/examples/Counter.ts @@ -50,54 +50,47 @@ export type ContractData = { export const builder = { message: { - in: { + in: (() => { // Creates a new `SetCount` message. - setCount: { - encode: (msg: SetCount): Cell => { + const setCount: CellCodec = { + encode: (msg: SetCount): Builder => { return beginCell() // break line .storeUint(opcodes.in.SetCount, 32) .storeUint(msg.queryId, 64) .storeUint(msg.newCount, 32) - .endCell() }, - decode: (cell: Cell): SetCount => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): SetCount => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), - newCount: s.loadUint(32), + queryId: src.loadUintBig(64), + newCount: src.loadUint(32), } }, - }, + } // Creates a new `IncreaseCount` message. - increaseCount: { - encode: (msg: IncreaseCount): Cell => { - return beginCell() - .storeUint(opcodes.in.IncreaseCount, 32) - .storeUint(msg.queryId, 64) - .endCell() + const increaseCount: CellCodec = { + encode: (msg: IncreaseCount): Builder => { + return beginCell().storeUint(opcodes.in.IncreaseCount, 32).storeUint(msg.queryId, 64) }, - decode: (cell: Cell): IncreaseCount => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): IncreaseCount => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), + queryId: src.loadUintBig(64), } }, - }, - }, + } + + return { setCount, increaseCount } + })(), }, data: (() => { // Creates a new `Counter_Data` contract data cell const contractData: CellCodec = { encode: (data: ContractData): Builder => { - let _pendingOwnerMaybe = data.ownable.pendingOwner - ? beginCell().storeAddress(data.ownable.pendingOwner) - : null - let ownable = beginCell() - .storeAddress(data.ownable.owner) - .storeMaybeBuilder(_pendingOwnerMaybe) - return beginCell().storeUint(data.id, 32).storeUint(data.value, 32).storeBuilder(ownable) + return beginCell() + .storeUint(data.id, 32) + .storeUint(data.value, 32) + .storeBuilder(ownable2step.builder.data.traitData.encode(data.ownable)) }, load: (src: Slice): ContractData => { const id = src.loadUintBig(32) @@ -135,7 +128,7 @@ export class ContractClient implements Contract, TypeAndVersion { } static newFrom(data: ContractData, code: Cell, workchain = 0): ContractClient { - const init = { code, data: builder.data.contractData.encode(data) } + const init = { code, data: builder.data.contractData.encode(data).asCell() } return new ContractClient(contractAddress(workchain, init), init) } @@ -153,7 +146,7 @@ export class ContractClient implements Contract, TypeAndVersion { } async sendSetCount(p: ContractProvider, via: Sender, value: bigint = 0n, body: SetCount) { - return this.sendInternal(p, via, value, builder.message.in.setCount.encode(body)) + return this.sendInternal(p, via, value, builder.message.in.setCount.encode(body).asCell()) } async sendIncreaseCount( diff --git a/contracts/wrappers/mcms/CallProxy.ts b/contracts/wrappers/mcms/CallProxy.ts index 3a3f4d083..0edb6df41 100644 --- a/contracts/wrappers/mcms/CallProxy.ts +++ b/contracts/wrappers/mcms/CallProxy.ts @@ -42,24 +42,23 @@ export enum Errors { export const builder = { message: { - in: { + in: (() => { // Creates a new `CallProxy_TopUp` message. - topUp: { - encode: (msg: TopUp): Cell => { + const topUp: CellCodec = { + encode: (msg: TopUp): Builder => { return beginCell() // break line .storeUint(opcodes.in.TopUp, 32) .storeUint(msg.queryId, 64) - .endCell() }, - decode: (cell: Cell): TopUp => { - const s = cell.beginParse() - s.skip(32) // skip opcode + load: (src: Slice): TopUp => { + src.skip(32) // skip opcode return { - queryId: s.loadUintBig(64), + queryId: src.loadUintBig(64), } }, - }, - }, + } + return { topUp } + })(), }, data: (() => { // Creates a new `CallProxy_Data` contract data cell From c1cc9c4be4a5446f95202cf30d55cdf6786c6880 Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Wed, 10 Sep 2025 15:55:32 -0300 Subject: [PATCH 3/8] fix: add missing .asCell() --- contracts/tests/ccip/CCIPRouter.spec.ts | 326 ++++++------ contracts/tests/mcms/BaseTest.ts | 8 +- contracts/tests/mcms/CallProxy.spec.ts | 10 +- contracts/tests/mcms/Integration.spec.ts | 480 ++++++++++-------- .../tests/mcms/ManyChainMultiSigBaseTest.ts | 58 ++- .../mcms/ManyChainMultiSigExecute.spec.ts | 178 ++++--- .../mcms/ManyChainMultiSigSetConfig.spec.ts | 198 ++++---- .../mcms/ManyChainMultiSigSetRoot.spec.ts | 92 ++-- .../mcms/ManyChainMultiSigSubgroups.spec.ts | 82 +-- contracts/tests/mcms/RBACTimelock.spec.ts | 186 ++++--- .../mcms/RBACTimelockBlockFunction.spec.ts | 8 +- .../tests/mcms/RBACTimelockCancel.spec.ts | 84 +-- .../tests/mcms/RBACTimelockExecute.spec.ts | 290 ++++++----- .../tests/mcms/RBACTimelockGetters.spec.ts | 254 +++++---- .../tests/mcms/RBACTimelockReceivable.spec.ts | 8 +- .../mcms/RBACTimelockScheduleBatch.spec.ts | 176 ++++--- .../mcms/RBACTimelockUpdateDelay.spec.ts | 12 +- contracts/wrappers/jetton/JettonCode.ts | 31 ++ contracts/wrappers/jetton/JettonMinter.ts | 2 +- contracts/wrappers/jetton/JettonWallet.ts | 2 +- 20 files changed, 1411 insertions(+), 1074 deletions(-) create mode 100644 contracts/wrappers/jetton/JettonCode.ts diff --git a/contracts/tests/ccip/CCIPRouter.spec.ts b/contracts/tests/ccip/CCIPRouter.spec.ts index c9d965cf3..a6e06611e 100644 --- a/contracts/tests/ccip/CCIPRouter.spec.ts +++ b/contracts/tests/ccip/CCIPRouter.spec.ts @@ -1,18 +1,20 @@ -import { Blockchain, BlockchainTransaction, SandboxContract, TreasuryContract } from '@ton/sandbox' -import { toNano, Address, Cell, Dictionary, Message, beginCell } from '@ton/core' +import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox' +import { toNano, Address, Cell, Dictionary, beginCell } from '@ton/core' import { compile } from '@ton/blueprint' -import { Router, RouterStorage } from '../../wrappers/ccip/Router' -import { OnRamp, OnRampStorage } from '../../wrappers/ccip/OnRamp' +import * as rt from '../../wrappers/ccip/Router' +import * as or from '../../wrappers/ccip/OnRamp' import { createTimestampedPriceValue, FeeQuoter, FeeQuoterStorage, - TimestampedPrice, } from '../../wrappers/ccip/FeeQuoter' import '@ton/test-utils' import { assertLog } from '../Logs' import { LogTypes } from '../../wrappers/ccip/Logs' import { ZERO_ADDRESS } from '../../src/utils' +import { JettonMinterCode, JettonWalletCode } from '../../wrappers/jetton/JettonCode' +import { JettonMinter } from '../../wrappers/jetton/JettonMinter' +import { JettonWallet, TransferMessage } from '../../wrappers/jetton/JettonWallet' const CHAINSEL_EVM_TEST_90000001 = 909606746561742123n const CHAINSEL_TON = 13879075125137744094n @@ -20,13 +22,15 @@ const CHAINSEL_TON = 13879075125137744094n describe('Router', () => { let blockchain: Blockchain let deployer: SandboxContract - let router: SandboxContract + let sender: SandboxContract + let router: SandboxContract let feeQuoter: SandboxContract - let onRamp: SandboxContract + let onRamp: SandboxContract beforeAll(async () => { blockchain = await Blockchain.create() deployer = await blockchain.treasury('deployer') + sender = await blockchain.treasury('sender') let deployerCode = await compile('Deployable') @@ -40,14 +44,24 @@ describe('Router', () => { blockchain.libs = libs // Mock UpdatePrices Message handler let routerCode = await compile('Router') - let data: RouterStorage = { + let data: rt.Storage = { ownable: { owner: deployer.address, pendingOwner: null, }, onRamp: ZERO_ADDRESS, } - router = blockchain.openContract(Router.createFromConfig(data, routerCode)) + router = blockchain.openContract(rt.Router.createFromConfig(data, routerCode)) + // Deploy contract + { + const result = await router.sendInternal(deployer.getSender(), toNano('1'), Cell.EMPTY) + expect(result.transactions).toHaveTransaction({ + from: deployer.address, + to: router.address, + deploy: true, + success: true, + }) + } // setup fee quoter { @@ -70,71 +84,83 @@ describe('Router', () => { } feeQuoter = blockchain.openContract(FeeQuoter.createFromConfig(data, code)) - let result = await feeQuoter.sendDeploy(deployer.getSender(), toNano('1')) - expect(result.transactions).toHaveTransaction({ - from: deployer.address, - to: feeQuoter.address, - deploy: true, - success: true, - }) - - result = await feeQuoter.sendUpdatePrices(deployer.getSender(), { - value: toNano('1'), - gasPrices: [], - tokenPrices: [{ token: ZERO_ADDRESS, price: 123n }], - }) - expect(result.transactions).toHaveTransaction({ - to: feeQuoter.address, - success: true, - }) + { + const result = await feeQuoter.sendDeploy(deployer.getSender(), toNano('1')) + expect(result.transactions).toHaveTransaction({ + from: deployer.address, + to: feeQuoter.address, + deploy: true, + success: true, + }) + } + { + const result = await feeQuoter.sendUpdatePrices(deployer.getSender(), { + value: toNano('1'), + msg: { + updates: { + gasPricesUpdates: [], + tokenPricesUpdates: [{ token: ZERO_ADDRESS, price: 123n }], + }, + }, + }) + expect(result.transactions).toHaveTransaction({ + to: feeQuoter.address, + success: true, + }) + } // add config for EVM destination - result = await feeQuoter.sendUpdateDestChainConfig(deployer.getSender(), { - value: toNano('1'), - destChainSelector: CHAINSEL_EVM_TEST_90000001, - config: { - // minimal valid config - isEnabled: true, - maxNumberOfTokensPerMsg: 0, // TODO: - maxDataBytes: 100, - maxPerMsgGasLimit: 100, - destGasOverhead: 0, - destGasPerPayloadByteBase: 0, - destGasPerPayloadByteHigh: 0, - destGasPerPayloadByteThreshold: 0, - destDataAvailabilityOverheadGas: 0, - destGasPerDataAvailabilityByte: 0, - destDataAvailabilityMultiplierBps: 0, - chainFamilySelector: 0, - enforceOutOfOrder: true, - defaultTokenFeeUsdCents: 0, - defaultTokenDestGasOverhead: 0, - defaultTxGasLimit: 1, - gasMultiplierWeiPerEth: 0n, - gasPriceStalenessThreshold: 0, - networkFeeUsdCents: 0, - }, - }) - expect(result.transactions).toHaveTransaction({ - to: feeQuoter.address, - success: true, - }) + { + const result = await feeQuoter.sendUpdateDestChainConfig(deployer.getSender(), { + value: toNano('1'), + msg: { + destChainSelector: CHAINSEL_EVM_TEST_90000001, + destChainConfig: { + // minimal valid config + isEnabled: true, + maxNumberOfTokensPerMsg: 0, // TODO: + maxDataBytes: 100, + maxPerMsgGasLimit: 100, + destGasOverhead: 0, + destGasPerPayloadByteBase: 0, + destGasPerPayloadByteHigh: 0, + destGasPerPayloadByteThreshold: 0, + destDataAvailabilityOverheadGas: 0, + destGasPerDataAvailabilityByte: 0, + destDataAvailabilityMultiplierBps: 0, + chainFamilySelector: 0, + enforceOutOfOrder: true, + defaultTokenFeeUsdCents: 0, + defaultTokenDestGasOverhead: 0, + defaultTxGasLimit: 1, + gasMultiplierWeiPerEth: 0n, + gasPriceStalenessThreshold: 0, + networkFeeUsdCents: 0, + }, + }, + }) + expect(result.transactions).toHaveTransaction({ + to: feeQuoter.address, + success: true, + }) + } // configure the feeToken - result = await feeQuoter.sendUpdateFeeTokens(deployer.getSender(), { - value: toNano('1'), - add: [{ token: ZERO_ADDRESS, premiumMultiplier: 1n }], - remove: [], - }) - expect(result.transactions).toHaveTransaction({ - to: feeQuoter.address, - success: true, - }) + { + const result = await feeQuoter.sendUpdateFeeTokens(deployer.getSender(), { + value: toNano('1'), + msg: { add: new Map([[ZERO_ADDRESS, { premiumMultiplierWeiPerEth: 1n }]]), remove: [] }, + }) + expect(result.transactions).toHaveTransaction({ + to: feeQuoter.address, + success: true, + }) + } // TODO: call UpdatePrices so there's a price available and the timestamp isn't zero } // setup onramp { let code = await compile('OnRamp') - let data: OnRampStorage = { + let data: or.OnRampStorage = { ownable: { owner: deployer.address, pendingOwner: null, @@ -148,101 +174,109 @@ describe('Router', () => { destChainConfigs: Dictionary.empty(Dictionary.Keys.BigUint(64), Dictionary.Values.Cell()), } // TODO: use deployable to make deterministic? - onRamp = blockchain.openContract(OnRamp.createFromConfig(data, code)) + onRamp = blockchain.openContract(or.OnRamp.createFromConfig(data, code)) + { + const result = await onRamp.sendDeploy(deployer.getSender(), toNano('1')) + expect(result.transactions).toHaveTransaction({ + from: deployer.address, + to: onRamp.address, + deploy: true, + success: true, + }) + } - let result = await onRamp.sendDeploy(deployer.getSender(), toNano('1')) + // add config for EVM destination + { + const result = await onRamp.sendUpdateDestChainConfigs(deployer.getSender(), { + value: toNano('1'), + destChainConfigs: [ + { + destChainSelector: CHAINSEL_EVM_TEST_90000001, + router: router.address, + allowlistEnabled: false, + }, + ], + }) + expect(result.transactions).toHaveTransaction({ + from: deployer.address, + to: onRamp.address, + deploy: false, + success: true, + }) + } + } + }) + + it('onramp arbitrary message passing', async () => { + // Configure onRamp on router + { + const result = await router.sendSetRamp(deployer.getSender(), { + value: toNano('1'), + queryID: 0, + destChainSelector: CHAINSEL_EVM_TEST_90000001, + onRamp: onRamp.address, + }) expect(result.transactions).toHaveTransaction({ from: deployer.address, - to: onRamp.address, - deploy: true, + to: router.address, success: true, }) + } - // add config for EVM destination - result = await onRamp.sendUpdateDestChainConfigs(deployer.getSender(), { + // router.ccipSend + { + const result = await router.sendCcipSend(sender.getSender(), { value: toNano('1'), - destChainConfigs: [ - { - destChainSelector: CHAINSEL_EVM_TEST_90000001, - router: router.address, - allowlistEnabled: false, - }, - ], + body: { + queryID: 1, + destChainSelector: CHAINSEL_EVM_TEST_90000001, + receiver: Buffer.alloc(64), + data: Cell.EMPTY, + tokenAmounts: [], + feeToken: ZERO_ADDRESS, + extraArgs: Cell.EMPTY, + }, }) + + // we called the router expect(result.transactions).toHaveTransaction({ - from: deployer.address, + from: sender.address, + to: router.address, + deploy: false, + success: true, + }) + // the router called the onRamp + expect(result.transactions).toHaveTransaction({ + from: router.address, to: onRamp.address, deploy: false, success: true, }) - } - }) + // assert message went to feeQuoter + expect(result.transactions).toHaveTransaction({ + from: onRamp.address, + to: feeQuoter.address, + deploy: false, + success: true, + }) - it('onramp', async () => { - // Configure onRamp on router - let result = await router.sendSetRamp(deployer.getSender(), { - value: toNano('1'), - queryID: 0, - destChainSelector: CHAINSEL_EVM_TEST_90000001, - onRamp: onRamp.address, - }) - expect(result.transactions).toHaveTransaction({ - from: deployer.address, - to: router.address, - deploy: true, // TRUE the first time around - success: true, - }) + // destChainConfig -> feeQuoter -> onRamp + expect(result.transactions).toHaveTransaction({ + from: feeQuoter.address, + to: onRamp.address, + deploy: false, + success: true, + }) - // router.ccipSend - result = await router.sendCcipSend(deployer.getSender(), { - value: toNano('1'), - queryID: 1, - destChainSelector: CHAINSEL_EVM_TEST_90000001, - receiver: Buffer.alloc(64), - data: Cell.EMPTY, - tokenAmounts: Cell.EMPTY, - feeToken: ZERO_ADDRESS, - extraArgs: Cell.EMPTY, - }) - - // we called the router - expect(result.transactions).toHaveTransaction({ - from: deployer.address, - to: router.address, - deploy: false, - success: true, - }) - // the router called the onRamp - expect(result.transactions).toHaveTransaction({ - from: router.address, - to: onRamp.address, - deploy: false, - success: true, - }) - // assert message went to feeQuoter - expect(result.transactions).toHaveTransaction({ - from: onRamp.address, - to: feeQuoter.address, - deploy: false, - success: true, - }) - - // destChainConfig -> feeQuoter -> onRamp - expect(result.transactions).toHaveTransaction({ - from: feeQuoter.address, - to: onRamp.address, - deploy: false, - success: true, - }) - - // assert CCIPMessageSent - assertLog(result.transactions, onRamp.address, LogTypes.CCIPMessageSent, { - message: { - header: { - destChainSelector: CHAINSEL_EVM_TEST_90000001, + // assert CCIPMessageSent + assertLog(result.transactions, onRamp.address, LogTypes.CCIPMessageSent, { + message: { + header: { + destChainSelector: CHAINSEL_EVM_TEST_90000001, + }, + sender: sender.address, }, - sender: deployer.address, - }, - }) + }) + } }) }) diff --git a/contracts/tests/mcms/BaseTest.ts b/contracts/tests/mcms/BaseTest.ts index e06ffbf0a..b048caf1b 100644 --- a/contracts/tests/mcms/BaseTest.ts +++ b/contracts/tests/mcms/BaseTest.ts @@ -72,7 +72,7 @@ export class BaseTestSetup { */ static singletonCalls(call: rbactl.Call): Cell { const calls = [call] - return asSnakeData(calls, (c) => rbactl.builder.data.call.encode(c).asBuilder()) + return asSnakeData(calls, (c) => rbactl.builder.data.call.encode(c)) } /** @@ -176,7 +176,7 @@ export class BaseTestSetup { const data = { id: crc32(`mcms.timelock.${testId}`), minDelay: BaseTestSetup.MIN_DELAY, - rbac: ac.builder.data.contractData.encode(rbacStorage), + rbac: ac.builder.data.contractData.encode(rbacStorage).asCell(), } this.bind.timelock = this.blockchain.openContract( @@ -219,7 +219,7 @@ export class BaseTestSetup { * Deploy the timelock contract and verify deployment */ async deployTimelockContract(): Promise { - const body = rbactl.builder.message.in.topUp.encode({ queryId: 1n }) + const body = rbactl.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const result = await this.bind.timelock.sendInternal( this.acc.deployer.getSender(), toNano('0.05'), @@ -288,7 +288,7 @@ export class BaseTestSetup { const result = await this.bind.timelock.sendInternal( this.acc.admin.getSender(), toNano('0.05'), - body, + body.asCell(), ) expect(result.transactions).toHaveTransaction({ from: this.acc.admin.address, diff --git a/contracts/tests/mcms/CallProxy.spec.ts b/contracts/tests/mcms/CallProxy.spec.ts index e3d36fb28..a67916f2d 100644 --- a/contracts/tests/mcms/CallProxy.spec.ts +++ b/contracts/tests/mcms/CallProxy.spec.ts @@ -39,7 +39,7 @@ describe('CallProxy', () => { const deployResult = await bind.callProxy.sendInternal( deployer.getSender(), toNano('0.05'), - callProxy.builder.message.in.topUp.encode({ queryId: 1n }), // TopUp message to deploy + callProxy.builder.message.in.topUp.encode({ queryId: 1n }).asCell(), // TopUp message to deploy ) expect(deployResult.transactions).toHaveTransaction({ @@ -72,7 +72,7 @@ describe('CallProxy', () => { const r = await bind.callProxy.sendInternal( deployer.getSender(), toNano('100.05'), - callProxy.builder.message.in.topUp.encode({ queryId: 1n }), + callProxy.builder.message.in.topUp.encode({ queryId: 1n }).asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -84,7 +84,7 @@ describe('CallProxy', () => { const r1 = await bind.callProxy.sendInternal( deployer.getSender(), toNano('0.05'), - callProxy.builder.message.in.topUp.encode({ queryId: 1n }), + callProxy.builder.message.in.topUp.encode({ queryId: 1n }).asCell(), ) expect(r1.transactions).toHaveTransaction({ @@ -96,7 +96,7 @@ describe('CallProxy', () => { const r2 = await bind.callProxy.sendInternal( deployer.getSender(), toNano('0.08'), - callProxy.builder.message.in.topUp.encode({ queryId: 1n }), + callProxy.builder.message.in.topUp.encode({ queryId: 1n }).asCell(), ) expect(r2.transactions).toHaveTransaction({ @@ -108,7 +108,7 @@ describe('CallProxy', () => { const r3 = await bind.callProxy.sendInternal( deployer.getSender(), toNano('0.08'), - callProxy.builder.message.in.topUp.encode({ queryId: 1n }), + callProxy.builder.message.in.topUp.encode({ queryId: 1n }).asCell(), ) expect(r3.transactions).toHaveTransaction({ diff --git a/contracts/tests/mcms/Integration.spec.ts b/contracts/tests/mcms/Integration.spec.ts index dd04b69dd..b6e5a9bab 100644 --- a/contracts/tests/mcms/Integration.spec.ts +++ b/contracts/tests/mcms/Integration.spec.ts @@ -179,7 +179,7 @@ describe('MCMS - IntegrationTest', () => { const data = { id: crc32('mcms.timelock.test-integration'), // unique ID for this instance minDelay: MIN_DELAY, - rbac: ac.builder.data.contractData.encode(rbacStorage), + rbac: ac.builder.data.contractData.encode(rbacStorage).asCell(), } bind.timelock = blockchain.openContract(rbactl.ContractClient.newFrom(data, code.timelock)) @@ -212,7 +212,7 @@ describe('MCMS - IntegrationTest', () => { // Deploy Timelock contract { - const body = rbactl.builder.message.in.topUp.encode({ queryId: 1n }) + const body = rbactl.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const r = await bind.timelock.sendInternal(acc.deployer.getSender(), toNano('0.05'), body) expect(r.transactions).toHaveTransaction({ @@ -229,11 +229,13 @@ describe('MCMS - IntegrationTest', () => { const r1 = await bind.ac.sendInternal( acc.deployer.getSender(), toNano('0.05'), - ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.admin, - account: bind.timelock.address, - }), + ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.admin, + account: bind.timelock.address, + }) + .asCell(), ) expect(r1.transactions).toHaveTransaction({ @@ -245,7 +247,7 @@ describe('MCMS - IntegrationTest', () => { // Set up (deploy, configure) MCMS contracts and transfer ownership to Timelock { - const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }) + const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const r = await bind.mcmsPropose.sendInternal(acc.deployer.getSender(), toNano('0.05'), body) expect(r.transactions).toHaveTransaction({ @@ -259,17 +261,19 @@ describe('MCMS - IntegrationTest', () => { const rSetConfig = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('0.2'), - mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: proposerKeyPairs().map((v) => uint8ArrayToBigInt(v.publicKey)), - signerGroups: Array(PROPOSE_COUNT).fill(0), - groupQuorums: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])).set( - 0, - PROPOSE_QUORUM, - ), - groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])), - clearRoot: false, - }), + mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: proposerKeyPairs().map((v) => uint8ArrayToBigInt(v.publicKey)), + signerGroups: Array(PROPOSE_COUNT).fill(0), + groupQuorums: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])).set( + 0, + PROPOSE_QUORUM, + ), + groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])), + clearRoot: false, + }) + .asCell(), ) expect(rSetConfig.transactions).toHaveTransaction({ @@ -285,7 +289,7 @@ describe('MCMS - IntegrationTest', () => { } { - const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }) + const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const result = await bind.mcmsVeto.sendInternal( acc.deployer.getSender(), toNano('0.05'), @@ -303,18 +307,20 @@ describe('MCMS - IntegrationTest', () => { const rSetConfig = await bind.mcmsVeto.sendInternal( acc.deployer.getSender(), toNano('0.2'), - mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: vetoKeyPairs().map((v) => uint8ArrayToBigInt(v.publicKey)), - signerGroups: Array(VETO_COUNT).fill(0), - groupQuorums: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])).set( - 0, - VETO_QUORUM, - ), - - groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])), - clearRoot: false, - }), + mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: vetoKeyPairs().map((v) => uint8ArrayToBigInt(v.publicKey)), + signerGroups: Array(VETO_COUNT).fill(0), + groupQuorums: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])).set( + 0, + VETO_QUORUM, + ), + + groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])), + clearRoot: false, + }) + .asCell(), ) expect(rSetConfig.transactions).toHaveTransaction({ @@ -330,7 +336,7 @@ describe('MCMS - IntegrationTest', () => { } { - const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }) + const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const result = await bind.mcmsBypass.sendInternal( acc.deployer.getSender(), toNano('0.05'), @@ -348,22 +354,24 @@ describe('MCMS - IntegrationTest', () => { const rSetConfig = await bind.mcmsBypass.sendInternal( acc.deployer.getSender(), toNano('0.2'), - mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: signerKeyPairs.map((v) => uint8ArrayToBigInt(v.publicKey)), - signerGroups: Array(PROPOSE_COUNT + VETO_COUNT) - .fill(1, 0, PROPOSE_COUNT) - .fill(2, PROPOSE_COUNT, PROPOSE_COUNT + VETO_COUNT), - groupQuorums: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])) - .set(0, 2) - .set(1, PROPOSE_QUORUM) - .set(2, VETO_QUORUM), - groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])) - .set(0, 0) - .set(1, 0) - .set(2, 0), - clearRoot: false, - }), + mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: signerKeyPairs.map((v) => uint8ArrayToBigInt(v.publicKey)), + signerGroups: Array(PROPOSE_COUNT + VETO_COUNT) + .fill(1, 0, PROPOSE_COUNT) + .fill(2, PROPOSE_COUNT, PROPOSE_COUNT + VETO_COUNT), + groupQuorums: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])) + .set(0, 2) + .set(1, PROPOSE_QUORUM) + .set(2, VETO_QUORUM), + groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])) + .set(0, 0) + .set(1, 0) + .set(2, 0), + clearRoot: false, + }) + .asCell(), ) expect(rSetConfig.transactions).toHaveTransaction({ @@ -380,7 +388,7 @@ describe('MCMS - IntegrationTest', () => { // Deploy CallProxy contract { - const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }) + const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const result = await bind.callProxy.sendInternal( acc.deployer.getSender(), toNano('0.05'), @@ -400,11 +408,13 @@ describe('MCMS - IntegrationTest', () => { const r1 = await bind.ac.sendInternal( acc.deployer.getSender(), toNano('0.05'), - ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.executor, - account: bind.callProxy.address, - }), + ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.executor, + account: bind.callProxy.address, + }) + .asCell(), ) expect(r1.transactions).toHaveTransaction({ @@ -440,25 +450,33 @@ describe('MCMS - IntegrationTest', () => { await ownable.sendInternal( acc.deployer.getSender(), toNano('0.05'), - ownable2step.builder.message.in.transferOwnership.encode({ - queryId: 1n, - newOwner: bind.timelock.address, - }), + ownable2step.builder.message.in.transferOwnership + .encode({ + queryId: 1n, + newOwner: bind.timelock.address, + }) + .asCell(), ) // Notice: using admin bypasser role to accept ownership transfer const result = await bind.timelock.sendInternal( acc.deployer.getSender(), toNano('0.10'), - rbactl.builder.message.in.bypasserExecuteBatch.encode({ - queryId: 1n, - // Notice: single call encoded as calls - calls: rbactl.builder.data.call.encode({ - target: ownable.address, - value: toNano('0.05'), - data: ownable2step.builder.message.in.acceptOwnership.encode({ queryId: 1n }), - }), - }), + rbactl.builder.message.in.bypasserExecuteBatch + .encode({ + queryId: 1n, + // Notice: single call encoded as calls + calls: rbactl.builder.data.call + .encode({ + target: ownable.address, + value: toNano('0.05'), + data: ownable2step.builder.message.in.acceptOwnership + .encode({ queryId: 1n }) + .asCell(), + }) + .asCell(), + }) + .asCell(), ) expect(result.transactions).toHaveTransaction({ @@ -511,15 +529,15 @@ describe('MCMS - IntegrationTest', () => { { target: bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), }, { target: bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }).asCell(), }, ], - (c) => rbactl.builder.data.call.encode(c).asBuilder(), + (c) => rbactl.builder.data.call.encode(c), ) const operationBatch: rbactl.OperationBatch = { @@ -548,13 +566,15 @@ describe('MCMS - IntegrationTest', () => { nonce: 0n, to: bind.timelock.address, value: toNano('0.05'), - data: rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: proposePredecessor, - salt: 0n, - delay: MIN_DELAY, - }), + data: rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: proposePredecessor, + salt: 0n, + delay: MIN_DELAY, + }) + .asCell(), }, ] const [setRoot, opProofs] = merkleProof.build( @@ -568,7 +588,7 @@ describe('MCMS - IntegrationTest', () => { const r = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.setRoot.encode(setRoot), + mcms.builder.message.in.setRoot.encode(setRoot).asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -580,11 +600,13 @@ describe('MCMS - IntegrationTest', () => { const r1 = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(ops[0]), - proof: opProofs[0], - }), + mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(ops[0]).asCell(), + proof: opProofs[0], + }) + .asCell(), ) expect(r1.transactions).toHaveTransaction({ @@ -598,12 +620,14 @@ describe('MCMS - IntegrationTest', () => { const r2 = await bind.callProxy.sendInternal( acc.deployer.getSender(), toNano('0.10'), - rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - predecessor: proposePredecessor, - salt: 0n, - calls, - }), + rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + predecessor: proposePredecessor, + salt: 0n, + calls, + }) + .asCell(), ) expect(r2.transactions).toHaveTransaction({ @@ -618,12 +642,14 @@ describe('MCMS - IntegrationTest', () => { const r3 = await bind.callProxy.sendInternal( acc.deployer.getSender(), toNano('1'), // TODO: notice the gas value required to pass is higher b/c reserveToncoinsOnBalance (check) - rbactl.builder.message.in.executeBatch.encode({ - queryId: 2n, - predecessor: proposePredecessor, - salt: 0n, - calls, - }), + rbactl.builder.message.in.executeBatch + .encode({ + queryId: 2n, + predecessor: proposePredecessor, + salt: 0n, + calls, + }) + .asCell(), ) expect(r3.transactions).toHaveTransaction({ @@ -672,13 +698,15 @@ describe('MCMS - IntegrationTest', () => { nonce: 1n, to: bind.timelock.address, value: toNano('0.05'), - data: rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: proposePredecessor, - salt: 0n, - delay: MIN_DELAY, - }), + data: rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: proposePredecessor, + salt: 0n, + delay: MIN_DELAY, + }) + .asCell(), }, ] @@ -693,7 +721,7 @@ describe('MCMS - IntegrationTest', () => { const r = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.setRoot.encode(setRoot), + mcms.builder.message.in.setRoot.encode(setRoot).asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -705,11 +733,13 @@ describe('MCMS - IntegrationTest', () => { const r1 = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(ops[0]), - proof: opProofs[0], - }), + mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(ops[0]).asCell(), + proof: opProofs[0], + }) + .asCell(), ) expect(r1.transactions).toHaveTransaction({ @@ -724,12 +754,14 @@ describe('MCMS - IntegrationTest', () => { const r2 = await bind.callProxy.sendInternal( acc.deployer.getSender(), toNano('0.80'), // TODO: notice the gas value required to pass is higher b/c reserveToncoinsOnBalance (check) - rbactl.builder.message.in.executeBatch.encode({ - queryId: 2n, - predecessor: proposePredecessor + 1n, // wrong predecessor - salt: 0n, - calls, - }), + rbactl.builder.message.in.executeBatch + .encode({ + queryId: 2n, + predecessor: proposePredecessor + 1n, // wrong predecessor + salt: 0n, + calls, + }) + .asCell(), ) expect(r2.transactions).toHaveTransaction({ @@ -748,12 +780,14 @@ describe('MCMS - IntegrationTest', () => { const r3 = await bind.callProxy.sendInternal( acc.deployer.getSender(), toNano('0.80'), // TODO: notice the gas value required to pass is higher b/c reserveToncoinsOnBalance (check) - rbactl.builder.message.in.executeBatch.encode({ - queryId: 3n, - predecessor: proposePredecessor, - salt: 0n, - calls, - }), + rbactl.builder.message.in.executeBatch + .encode({ + queryId: 3n, + predecessor: proposePredecessor, + salt: 0n, + calls, + }) + .asCell(), ) expect(r3.transactions).toHaveTransaction({ @@ -777,10 +811,10 @@ describe('MCMS - IntegrationTest', () => { { target: bind.timelock.address, value: toNano('0.05'), - data: rbactl.builder.message.in.updateDelay.encode({ queryId: 1n, newDelay }), + data: rbactl.builder.message.in.updateDelay.encode({ queryId: 1n, newDelay }).asCell(), }, ], - (c) => rbactl.builder.data.call.encode(c).asBuilder(), + (c) => rbactl.builder.data.call.encode(c), ) const operationBatch: rbactl.OperationBatch = { @@ -809,7 +843,9 @@ describe('MCMS - IntegrationTest', () => { nonce: 0n, to: bind.timelock.address, value: toNano('0.05'), - data: rbactl.builder.message.in.bypasserExecuteBatch.encode({ queryId: 1n, calls }), + data: rbactl.builder.message.in.bypasserExecuteBatch + .encode({ queryId: 1n, calls }) + .asCell(), }, ] @@ -824,7 +860,7 @@ describe('MCMS - IntegrationTest', () => { const r = await bind.mcmsBypass.sendInternal( acc.deployer.getSender(), toNano('0.20'), - mcms.builder.message.in.setRoot.encode(setRoot), + mcms.builder.message.in.setRoot.encode(setRoot).asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -836,11 +872,13 @@ describe('MCMS - IntegrationTest', () => { const r1 = await bind.mcmsBypass.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(ops[0]), - proof: opProofs[0], - }), + mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(ops[0]).asCell(), + proof: opProofs[0], + }) + .asCell(), ) expect(r1.transactions).toHaveTransaction({ @@ -874,7 +912,7 @@ describe('MCMS - IntegrationTest', () => { { // Notice: we need to add funds or test fails with 'Not enough Toncoin' - const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }) + const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const r = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('1.00'), @@ -892,14 +930,16 @@ describe('MCMS - IntegrationTest', () => { { target: bind.timelock.address, value: toNano('0.05'), - data: ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.admin, - account: evil, - }), + data: ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.admin, + account: evil, + }) + .asCell(), }, ], - (c) => rbactl.builder.data.call.encode(c).asBuilder(), + (c) => rbactl.builder.data.call.encode(c), ) const operationBatch: rbactl.OperationBatch = { @@ -928,13 +968,15 @@ describe('MCMS - IntegrationTest', () => { nonce: 2n, to: bind.timelock.address, value: toNano('0.05'), - data: rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: proposePredecessor, - salt: 0n, - delay: MIN_DELAY, - }), + data: rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: proposePredecessor, + salt: 0n, + delay: MIN_DELAY, + }) + .asCell(), }, ] @@ -949,7 +991,7 @@ describe('MCMS - IntegrationTest', () => { const r = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('0.20'), - mcms.builder.message.in.setRoot.encode(setRoot), + mcms.builder.message.in.setRoot.encode(setRoot).asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -961,11 +1003,13 @@ describe('MCMS - IntegrationTest', () => { const r1 = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(ops[0]), - proof: opProofs[0], - }), + mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(ops[0]).asCell(), + proof: opProofs[0], + }) + .asCell(), ) expect(r1.transactions).toHaveTransaction({ @@ -977,12 +1021,14 @@ describe('MCMS - IntegrationTest', () => { const r2 = await bind.callProxy.sendInternal( acc.deployer.getSender(), toNano('0.10'), - rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - predecessor: proposePredecessor, - salt: 0n, - calls, - }), + rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + predecessor: proposePredecessor, + salt: 0n, + calls, + }) + .asCell(), ) expect(r2.transactions).toHaveTransaction({ @@ -1014,7 +1060,7 @@ describe('MCMS - IntegrationTest', () => { nonce: 0n, to: bind.timelock.address, value: toNano('0.05'), - data: rbactl.builder.message.in.cancel.encode({ queryId: 1n, id: callsHash }), + data: rbactl.builder.message.in.cancel.encode({ queryId: 1n, id: callsHash }).asCell(), }, ] @@ -1029,7 +1075,7 @@ describe('MCMS - IntegrationTest', () => { const r = await bind.mcmsVeto.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.setRoot.encode(setRoot), + mcms.builder.message.in.setRoot.encode(setRoot).asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -1041,11 +1087,13 @@ describe('MCMS - IntegrationTest', () => { const r1 = await bind.mcmsVeto.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(ops[0]), - proof: opProofs[0], - }), + mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(ops[0]).asCell(), + proof: opProofs[0], + }) + .asCell(), ) expect(r1.transactions).toHaveTransaction({ @@ -1061,12 +1109,14 @@ describe('MCMS - IntegrationTest', () => { const r2 = await bind.callProxy.sendInternal( acc.deployer.getSender(), toNano('0.10'), - rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - predecessor: proposePredecessor, - salt: 0n, - calls, - }), + rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + predecessor: proposePredecessor, + salt: 0n, + calls, + }) + .asCell(), ) expect(r2.transactions).toHaveTransaction({ @@ -1088,35 +1138,37 @@ describe('MCMS - IntegrationTest', () => { { target: bind.mcmsPropose.address, value: toNano('0.2'), - data: mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: proposerKeyPairs().map((v) => uint8ArrayToBigInt(v.publicKey)), - signerGroups: Array(PROPOSE_COUNT).fill(0), - groupQuorums: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])).set( - 0, - PROPOSE_QUORUM - 1, - ), - groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])), - clearRoot: false, - }), + data: mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: proposerKeyPairs().map((v) => uint8ArrayToBigInt(v.publicKey)), + signerGroups: Array(PROPOSE_COUNT).fill(0), + groupQuorums: new Map( + Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0]), + ).set(0, PROPOSE_QUORUM - 1), + groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])), + clearRoot: false, + }) + .asCell(), }, { target: bind.mcmsVeto.address, value: toNano('0.2'), - data: mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: vetoKeyPairs().map((v) => uint8ArrayToBigInt(v.publicKey)), - signerGroups: Array(VETO_COUNT).fill(0), - groupQuorums: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])).set( - 0, - VETO_QUORUM - 1, - ), - groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])), - clearRoot: false, - }), + data: mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: vetoKeyPairs().map((v) => uint8ArrayToBigInt(v.publicKey)), + signerGroups: Array(VETO_COUNT).fill(0), + groupQuorums: new Map( + Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0]), + ).set(0, VETO_QUORUM - 1), + groupParents: new Map(Array.from({ length: MCMS_NUM_GROUPS }, (_, i) => [i, 0])), + clearRoot: false, + }) + .asCell(), }, ], - (c) => rbactl.builder.data.call.encode(c).asBuilder(), + (c) => rbactl.builder.data.call.encode(c), ) const operationBatch: rbactl.OperationBatch = { @@ -1145,13 +1197,15 @@ describe('MCMS - IntegrationTest', () => { nonce: 3n, to: bind.timelock.address, value: toNano('0.05'), - data: rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: proposePredecessor, - salt: 0n, - delay: MIN_DELAY, - }), + data: rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: proposePredecessor, + salt: 0n, + delay: MIN_DELAY, + }) + .asCell(), }, ] @@ -1166,7 +1220,7 @@ describe('MCMS - IntegrationTest', () => { const r = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.setRoot.encode(setRoot), + mcms.builder.message.in.setRoot.encode(setRoot).asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -1178,11 +1232,13 @@ describe('MCMS - IntegrationTest', () => { const r1 = await bind.mcmsPropose.sendInternal( acc.deployer.getSender(), toNano('0.10'), - mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(ops[0]), - proof: opProofs[0], - }), + mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(ops[0]).asCell(), + proof: opProofs[0], + }) + .asCell(), ) expect(r1.transactions).toHaveTransaction({ @@ -1196,12 +1252,14 @@ describe('MCMS - IntegrationTest', () => { const r2 = await bind.callProxy.sendInternal( acc.deployer.getSender(), toNano('0.80'), // TODO: notice the gas value required to pass is higher b/c reserveToncoinsOnBalance (check) - rbactl.builder.message.in.executeBatch.encode({ - queryId: 2n, - predecessor: proposePredecessor, - salt: 0n, - calls, - }), + rbactl.builder.message.in.executeBatch + .encode({ + queryId: 2n, + predecessor: proposePredecessor, + salt: 0n, + calls, + }) + .asCell(), ) expect(r2.transactions).toHaveTransaction({ diff --git a/contracts/tests/mcms/ManyChainMultiSigBaseTest.ts b/contracts/tests/mcms/ManyChainMultiSigBaseTest.ts index 02a5e3d27..411286bfd 100644 --- a/contracts/tests/mcms/ManyChainMultiSigBaseTest.ts +++ b/contracts/tests/mcms/ManyChainMultiSigBaseTest.ts @@ -197,11 +197,13 @@ export class MCMSBaseTestSetup { const signers = new Map() for (let i = 0; i < this.testSigners.length; i++) { const signer = this.testSigners[i] - const signerData = mcms.builder.data.signer.encode({ - key: BigInt('0x' + signer.keyPair.publicKey.toString('hex')), - index: signer.index, - group: signer.group, - }) + const signerData = mcms.builder.data.signer + .encode({ + key: BigInt('0x' + signer.keyPair.publicKey.toString('hex')), + index: signer.index, + group: signer.group, + }) + .asCell() signers.set(i, signerData.toBoc()) } @@ -276,7 +278,7 @@ export class MCMSBaseTestSetup { * Deploy the MCMS contract and verify deployment */ async deployMCMSContract(): Promise { - const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }) + const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const result = await this.bind.mcms.sendInternal( this.acc.deployer.getSender(), toNano('2'), @@ -331,14 +333,16 @@ export class MCMSBaseTestSetup { // Build signer groups cell - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: this.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: this.testSigners.map((s) => s.group), - groupQuorums: this.testConfig.groupQuorums, - groupParents: this.testConfig.groupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: this.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: this.testSigners.map((s) => s.group), + groupQuorums: this.testConfig.groupQuorums, + groupParents: this.testConfig.groupParents, + clearRoot: false, + }) + .asCell() const result = await this.bind.mcms.sendInternal( this.acc.multisigOwner.getSender(), @@ -383,16 +387,18 @@ export class MCMSBaseTestSetup { i == MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX ? toNano('10') : toNano('0.10') // default op - let op = counter.builder.message.in.setCount.encode({ - queryId: BigInt(i + startNonce), - newCount: i + startNonce, - }) + let op = counter.builder.message.in.setCount + .encode({ + queryId: BigInt(i + startNonce), + newCount: i + startNonce, + }) + .asCell() { switch (i) { case MCMSBaseSetRootAndExecuteTestSetup.REVERTING_OP_INDEX: if (includeRevertingOp) { - op = beginCell().storeUint(0xffffffff, 32).endCell() + op = beginCell().storeUint(0xffffffff, 32).asCell() } else { // use default op } @@ -550,7 +556,7 @@ export class MCMSBaseSetRootAndExecuteTestSetup extends MCMSBaseTestSetup { // Store the operation proofs for later use in execute tests this.opProofs = opProofs - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await this.bind.mcms.sendInternal( this.acc.deployer.getSender(), @@ -578,11 +584,13 @@ export class MCMSBaseSetRootAndExecuteTestSetup extends MCMSBaseTestSetup { // Execute all operations up to the post-op count limit to simulate setOpCount async executeOperationsUpTo(index: number) { for (let i = 0; i < index; i++) { - const executeBody = mcms.builder.message.in.execute.encode({ - queryId: BigInt(i + 1), - op: mcms.builder.data.op.encode(this.testOps[i]), - proof: this.opProofs[i], - }) + const executeBody = mcms.builder.message.in.execute + .encode({ + queryId: BigInt(i + 1), + op: mcms.builder.data.op.encode(this.testOps[i]).asCell(), + proof: this.opProofs[i], + }) + .asCell() const result = await this.bind.mcms.sendInternal( this.acc.deployer.getSender(), diff --git a/contracts/tests/mcms/ManyChainMultiSigExecute.spec.ts b/contracts/tests/mcms/ManyChainMultiSigExecute.spec.ts index a937e4beb..888660e84 100644 --- a/contracts/tests/mcms/ManyChainMultiSigExecute.spec.ts +++ b/contracts/tests/mcms/ManyChainMultiSigExecute.spec.ts @@ -24,7 +24,7 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('10'), - mcms.builder.message.in.topUp.encode({ queryId: 1n }), + mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell(), ) await baseTest.recreateTestOpsNoRevertingOp() @@ -38,11 +38,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { // Use any operation and proof - they won't even be checked const fakeOp = baseTest.testOps[0] - const executeBody = mcms.builder.message.in.execute.encode({ - queryId: 999n, - op: mcms.builder.data.op.encode(fakeOp), - proof: [], // fakeProof - }) + const executeBody = mcms.builder.message.in.execute + .encode({ + queryId: 999n, + op: mcms.builder.data.op.encode(fakeOp).asCell(), + proof: [], // fakeProof + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -64,11 +66,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { modifiedOp.value = modifiedOp.value + 1n // Try with empty proof first - const executeBody1 = mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(modifiedOp), - proof: [], // emptyProof - }) + const executeBody1 = mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(modifiedOp).asCell(), + proof: [], // emptyProof + }) + .asCell() const result1 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -86,11 +90,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { // Send a proof for the original op before the modification - should still fail const originalProof = baseTest.getProofForOp(0) - const executeBody2 = mcms.builder.message.in.execute.encode({ - queryId: 2n, - op: mcms.builder.data.op.encode(modifiedOp), - proof: originalProof, - }) + const executeBody2 = mcms.builder.message.in.execute + .encode({ + queryId: 2n, + op: mcms.builder.data.op.encode(modifiedOp).asCell(), + proof: originalProof, + }) + .asCell() const result2 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -114,11 +120,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { const wrongChainIdOp = { ...baseTest.testOps[0] } wrongChainIdOp.chainId = wrongChainIdOp.chainId + 1n - const executeBody1 = mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(wrongChainIdOp), - proof: dummyProof, - }) + const executeBody1 = mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(wrongChainIdOp).asCell(), + proof: dummyProof, + }) + .asCell() const result1 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -137,11 +145,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { const wrongMultiSigOp = { ...baseTest.testOps[0] } wrongMultiSigOp.multiSig = baseTest.acc.multisigOwner.address - const executeBody2 = mcms.builder.message.in.execute.encode({ - queryId: 2n, - op: mcms.builder.data.op.encode(wrongMultiSigOp), - proof: dummyProof, - }) + const executeBody2 = mcms.builder.message.in.execute + .encode({ + queryId: 2n, + op: mcms.builder.data.op.encode(wrongMultiSigOp).asCell(), + proof: dummyProof, + }) + .asCell() const result2 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -160,11 +170,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { const wrongNonceOp = { ...baseTest.testOps[0] } wrongNonceOp.nonce = wrongNonceOp.nonce + 1n - const executeBody3 = mcms.builder.message.in.execute.encode({ - queryId: 3n, - op: mcms.builder.data.op.encode(wrongNonceOp), - proof: dummyProof, - }) + const executeBody3 = mcms.builder.message.in.execute + .encode({ + queryId: 3n, + op: mcms.builder.data.op.encode(wrongNonceOp).asCell(), + proof: dummyProof, + }) + .asCell() const result3 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -182,11 +194,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { // Test 4: Expired root (advance time past validUntil) baseTest.warpTime(MCMSBaseSetRootAndExecuteTestSetup.TEST_VALID_UNTIL + 1) - const executeBody4 = mcms.builder.message.in.execute.encode({ - queryId: 4n, - op: mcms.builder.data.op.encode(baseTest.testOps[0]), - proof: dummyProof, - }) + const executeBody4 = mcms.builder.message.in.execute + .encode({ + queryId: 4n, + op: mcms.builder.data.op.encode(baseTest.testOps[0]).asCell(), + proof: dummyProof, + }) + .asCell() const result4 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -206,11 +220,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { // Execute first operation const proof1 = baseTest.getProofForOp(0) - const executeBody1 = mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(baseTest.testOps[0]), - proof: proof1, - }) + const executeBody1 = mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(baseTest.testOps[0]).asCell(), + proof: proof1, + }) + .asCell() const result1 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -245,11 +261,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { // Try to execute the third op instead of the second - should fail with WrongNonce const proof3 = baseTest.getProofForOp(2) - const executeBody3 = mcms.builder.message.in.execute.encode({ - queryId: 3n, - op: mcms.builder.data.op.encode(baseTest.testOps[2]), - proof: proof3, - }) + const executeBody3 = mcms.builder.message.in.execute + .encode({ + queryId: 3n, + op: mcms.builder.data.op.encode(baseTest.testOps[2]).asCell(), + proof: proof3, + }) + .asCell() const result3Early = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -267,11 +285,13 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { // Execute the second op correctly const proof2 = baseTest.getProofForOp(1) - const executeBody2 = mcms.builder.message.in.execute.encode({ - queryId: 2n, - op: mcms.builder.data.op.encode(baseTest.testOps[1]), - proof: proof2, - }) + const executeBody2 = mcms.builder.message.in.execute + .encode({ + queryId: 2n, + op: mcms.builder.data.op.encode(baseTest.testOps[1]).asCell(), + proof: proof2, + }) + .asCell() const result2 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -302,13 +322,15 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { // Now try to execute the reverting operation const proof = baseTest.getProofForOp(MCMSBaseSetRootAndExecuteTestSetup.REVERTING_OP_INDEX) - const executeBody = mcms.builder.message.in.execute.encode({ - queryId: BigInt(MCMSBaseSetRootAndExecuteTestSetup.REVERTING_OP_INDEX + 1), - op: mcms.builder.data.op.encode( - baseTest.testOps[MCMSBaseSetRootAndExecuteTestSetup.REVERTING_OP_INDEX], - ), - proof, - }) + const executeBody = mcms.builder.message.in.execute + .encode({ + queryId: BigInt(MCMSBaseSetRootAndExecuteTestSetup.REVERTING_OP_INDEX + 1), + op: mcms.builder.data.op + .encode(baseTest.testOps[MCMSBaseSetRootAndExecuteTestSetup.REVERTING_OP_INDEX]) + .asCell(), + proof, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -359,13 +381,15 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { // Try to execute value operation without sufficient balance const proof = baseTest.getProofForOp(MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX) - const executeBody = mcms.builder.message.in.execute.encode({ - queryId: BigInt(MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX + 1), - op: mcms.builder.data.op.encode( - baseTest.testOps[MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX], - ), - proof, - }) + const executeBody = mcms.builder.message.in.execute + .encode({ + queryId: BigInt(MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX + 1), + op: mcms.builder.data.op + .encode(baseTest.testOps[MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX]) + .asCell(), + proof, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -396,21 +420,25 @@ describe('MCMS - ManyChainMultiSigExecuteTest', () => { // Execute the value operation const proof = baseTest.getProofForOp(MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX) - const executeBody = mcms.builder.message.in.execute.encode({ - queryId: BigInt(MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX + 1), - op: mcms.builder.data.op.encode( - baseTest.testOps[MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX], - ), - proof, - }) + const executeBody = mcms.builder.message.in.execute + .encode({ + queryId: BigInt(MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX + 1), + op: mcms.builder.data.op + .encode(baseTest.testOps[MCMSBaseSetRootAndExecuteTestSetup.VALUE_OP_INDEX]) + .asCell(), + proof, + }) + .asCell() // TopUp contract before execution operation await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('10'), - mcms.builder.message.in.topUp.encode({ - queryId: 1n, - }), + mcms.builder.message.in.topUp + .encode({ + queryId: 1n, + }) + .asCell(), ) const result = await baseTest.bind.mcms.sendInternal( diff --git a/contracts/tests/mcms/ManyChainMultiSigSetConfig.spec.ts b/contracts/tests/mcms/ManyChainMultiSigSetConfig.spec.ts index 2bf11fee0..a16a57bba 100644 --- a/contracts/tests/mcms/ManyChainMultiSigSetConfig.spec.ts +++ b/contracts/tests/mcms/ManyChainMultiSigSetConfig.spec.ts @@ -24,14 +24,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { it('should fail if non-owner tries to set config', async () => { // Try to call setConfig from non-owner address (should fail) - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: baseTest.testSigners.map((s) => s.group), - groupQuorums: baseTest.testGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: baseTest.testSigners.map((s) => s.group), + groupQuorums: baseTest.testGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -53,14 +55,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { const emptySignerKeys = emptySignerList.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)) const emptySignerGroups = emptySignerList.map((s) => s.group) - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: emptySignerKeys, - signerGroups: emptySignerGroups, - groupQuorums: baseTest.testGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: emptySignerKeys, + signerGroups: emptySignerGroups, + groupQuorums: baseTest.testGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), @@ -84,14 +88,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { const signerKeys = duplicateSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)) const signerGroups = duplicateSigners.map((s) => s.group) - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys, - signerGroups, - groupQuorums: baseTest.testGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys, + signerGroups, + groupQuorums: baseTest.testGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), @@ -115,14 +121,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { const signerKeys = invalidGroupSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)) const signerGroups = invalidGroupSigners.map((s) => s.group) - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys, - signerGroups, - groupQuorums: baseTest.testGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys, + signerGroups, + groupQuorums: baseTest.testGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), @@ -149,14 +157,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { } } - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: baseTest.testSigners.map((s) => s.group), - groupQuorums: invalidGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: baseTest.testSigners.map((s) => s.group), + groupQuorums: invalidGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), @@ -183,14 +193,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { } } - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: baseTest.testSigners.map((s) => s.group), - groupQuorums: baseTest.testGroupQuorums, - groupParents: invalidGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: baseTest.testSigners.map((s) => s.group), + groupQuorums: baseTest.testGroupQuorums, + groupParents: invalidGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), @@ -217,14 +229,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { } } - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: baseTest.testSigners.map((s) => s.group), - groupQuorums: baseTest.testGroupQuorums, - groupParents: invalidGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: baseTest.testSigners.map((s) => s.group), + groupQuorums: baseTest.testGroupQuorums, + groupParents: invalidGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), @@ -245,14 +259,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { const disabledGroupSigners = [...baseTest.testSigners] disabledGroupSigners[1].group = mcms.NUM_GROUPS - 1 // Last group should be disabled - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: disabledGroupSigners.map((s) => s.group), - groupQuorums: baseTest.testGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: disabledGroupSigners.map((s) => s.group), + groupQuorums: baseTest.testGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), @@ -273,14 +289,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { const signer = baseTest.testSigners.slice(0, 4) const shorterSignerGroup = baseTest.testSigners.slice(0, 3) - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: signer.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: shorterSignerGroup.map((s) => s.group), - groupQuorums: baseTest.testGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: signer.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: shorterSignerGroup.map((s) => s.group), + groupQuorums: baseTest.testGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), @@ -297,14 +315,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { }) it('should successfully set config without clearing root', async () => { - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: baseTest.testSigners.map((s) => s.group), - groupQuorums: baseTest.testGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: baseTest.testSigners.map((s) => s.group), + groupQuorums: baseTest.testGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), @@ -340,14 +360,16 @@ describe('MCMS - ManyChainMultiSigSetConfigTest', () => { }) it('should successfully set config and clear root', async () => { - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: baseTest.testSigners.map((s) => s.group), - groupQuorums: baseTest.testGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: true, // Clear the root - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: baseTest.testSigners.map((s) => s.group), + groupQuorums: baseTest.testGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: true, // Clear the root + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), diff --git a/contracts/tests/mcms/ManyChainMultiSigSetRoot.spec.ts b/contracts/tests/mcms/ManyChainMultiSigSetRoot.spec.ts index b2636a14c..b47050700 100644 --- a/contracts/tests/mcms/ManyChainMultiSigSetRoot.spec.ts +++ b/contracts/tests/mcms/ManyChainMultiSigSetRoot.spec.ts @@ -41,7 +41,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -73,7 +73,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -106,7 +106,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -141,7 +141,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -164,7 +164,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('10'), - mcms.builder.message.in.topUp.encode({ queryId: 1n }), + mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell(), ) await baseTest.executeOperationsUpTo(MCMSBaseSetRootAndExecuteTestSetup.OPS_NUM) @@ -185,7 +185,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -217,7 +217,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -251,7 +251,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -279,7 +279,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -307,7 +307,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -358,7 +358,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -382,7 +382,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('10'), - mcms.builder.message.in.topUp.encode({ queryId: 1n }), + mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell(), ) expect(result.transactions).toHaveTransaction({ from: baseTest.acc.deployer.address, @@ -408,7 +408,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -435,14 +435,16 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { await baseTest.executeOperationsUpTo(Number(targetOpCount)) // Set config with clearRoot = true - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: baseTest.testSigners.map((s) => s.group), - groupQuorums: baseTest.testGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: true, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: baseTest.testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: baseTest.testSigners.map((s) => s.group), + groupQuorums: baseTest.testGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: true, + }) + .asCell() { const result = await baseTest.bind.mcms.sendInternal( @@ -474,7 +476,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -505,7 +507,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) // TODO: Original test doesn't add this 1, but this test fails with ERROR_SIGNED_HASH_ALREADY_SEEN if we don't. Thats probably a bug? Should the "override previous root" be used to calculate the hash? Or maybe it is a problem in the order of validations - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -540,7 +542,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -566,7 +568,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -607,7 +609,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -642,7 +644,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) setRoot.metadata = corruptedMetadata - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -674,7 +676,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) setRoot.metadata = corruptedMetadata - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('0.05'), @@ -705,7 +707,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) setRoot.metadata = corruptedMetadata - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('0.05'), @@ -736,7 +738,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) setRoot.metadata = corruptedMetadata - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('0.05'), @@ -768,7 +770,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) setRoot.metadata = corruptedMetadata - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('0.05'), @@ -807,14 +809,16 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { // set the new partition of signers to groups { - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: signers.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups, - groupQuorums: stricterGroupQuorums, - groupParents: baseTest.testGroupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: signers.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups, + groupQuorums: stricterGroupQuorums, + groupParents: baseTest.testGroupParents, + clearRoot: false, + }) + .asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), toNano('1'), @@ -858,7 +862,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('1'), @@ -880,7 +884,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('0.05'), @@ -908,7 +912,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { baseTest.testOps, MCMSBaseSetRootAndExecuteTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('0.05'), @@ -948,7 +952,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { setRoot.root = corruptSetRoot.root // Use old signatures (invalid for new root) - const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot) + const setRootBody = mcms.builder.message.in.setRoot.encode(setRoot).asCell() const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('0.05'), @@ -980,7 +984,7 @@ describe('MCMS - ManyChainMultiSigSetRootTest', () => { const result = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('0.05'), - mcms.builder.message.in.setRoot.encode(setRoot), + mcms.builder.message.in.setRoot.encode(setRoot).asCell(), ) expect(result.transactions).toHaveTransaction({ diff --git a/contracts/tests/mcms/ManyChainMultiSigSubgroups.spec.ts b/contracts/tests/mcms/ManyChainMultiSigSubgroups.spec.ts index 56b4dd2ea..9e693b2e3 100644 --- a/contracts/tests/mcms/ManyChainMultiSigSubgroups.spec.ts +++ b/contracts/tests/mcms/ManyChainMultiSigSubgroups.spec.ts @@ -98,7 +98,7 @@ describe('MCMS - ManyChainMultiSigSubgroupsTest', () => { } // Deploy MCMS contract - const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }) + const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const deployResult = await bind.mcms.sendInternal(acc.deployer.getSender(), toNano('2'), body) expect(deployResult.transactions).toHaveTransaction({ @@ -155,14 +155,16 @@ describe('MCMS - ManyChainMultiSigSubgroupsTest', () => { // Set configuration { - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: testSigners.map((s) => s.group), - groupQuorums, - groupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: testSigners.map((s) => s.group), + groupQuorums, + groupParents, + clearRoot: false, + }) + .asCell() const result = await bind.mcms.sendInternal( acc.multisigOwner.getSender(), @@ -211,7 +213,9 @@ describe('MCMS - ManyChainMultiSigSubgroupsTest', () => { MCMSBaseTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const insufficientSetRootBody = mcms.builder.message.in.setRoot.encode(insufficientSetRoot) + const insufficientSetRootBody = mcms.builder.message.in.setRoot + .encode(insufficientSetRoot) + .asCell() const insufficientResult = await bind.mcms.sendInternal( acc.deployer.getSender(), toNano('0.5'), @@ -290,14 +294,16 @@ describe('MCMS - ManyChainMultiSigSubgroupsTest', () => { { const signers = testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)) - const setConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: signers, - signerGroups: testSigners.map((s) => s.group), - groupQuorums, - groupParents, - clearRoot: false, - }) + const setConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: signers, + signerGroups: testSigners.map((s) => s.group), + groupQuorums, + groupParents, + clearRoot: false, + }) + .asCell() const result = await bind.mcms.sendInternal( acc.multisigOwner.getSender(), @@ -365,7 +371,7 @@ describe('MCMS - ManyChainMultiSigSubgroupsTest', () => { MCMSBaseTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const reducedSetRootBody = mcms.builder.message.in.setRoot.encode(reducedSetRoot) + const reducedSetRootBody = mcms.builder.message.in.setRoot.encode(reducedSetRoot).asCell() const result = await bind.mcms.sendInternal( acc.deployer.getSender(), toNano('0.5'), @@ -401,7 +407,7 @@ describe('MCMS - ManyChainMultiSigSubgroupsTest', () => { MCMSBaseTestSetup.OP_FINALIZATION_TIMEOUT_ZERO, ) - const overrideSetRootBody = mcms.builder.message.in.setRoot.encode(overrideSetRoot) + const overrideSetRootBody = mcms.builder.message.in.setRoot.encode(overrideSetRoot).asCell() const overrideResult = await bind.mcms.sendInternal( acc.deployer.getSender(), toNano('0.5'), @@ -435,14 +441,16 @@ describe('MCMS - ManyChainMultiSigSubgroupsTest', () => { const signerGroupsData = testSigners.map((s) => s.group) // Test malformed configuration should fail - const malformedSetConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 1n, - signerKeys: testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: signerGroupsData, - groupQuorums, - groupParents: malformedGroupParents, - clearRoot: false, - }) + const malformedSetConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 1n, + signerKeys: testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: signerGroupsData, + groupQuorums, + groupParents: malformedGroupParents, + clearRoot: false, + }) + .asCell() const malformedResult = await bind.mcms.sendInternal( acc.multisigOwner.getSender(), @@ -464,14 +472,16 @@ describe('MCMS - ManyChainMultiSigSubgroupsTest', () => { correctGroupParents.set(i, i - 1) // Each group's parent is the previous one } - const correctSetConfigBody = mcms.builder.message.in.setConfig.encode({ - queryId: 2n, - signerKeys: testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), - signerGroups: signerGroupsData, - groupQuorums, - groupParents: correctGroupParents, - clearRoot: false, - }) + const correctSetConfigBody = mcms.builder.message.in.setConfig + .encode({ + queryId: 2n, + signerKeys: testSigners.map((s) => uint8ArrayToBigInt(s.keyPair.publicKey)), + signerGroups: signerGroupsData, + groupQuorums, + groupParents: correctGroupParents, + clearRoot: false, + }) + .asCell() const correctResult = await bind.mcms.sendInternal( acc.multisigOwner.getSender(), diff --git a/contracts/tests/mcms/RBACTimelock.spec.ts b/contracts/tests/mcms/RBACTimelock.spec.ts index 3b1ba35c8..a9da842e2 100644 --- a/contracts/tests/mcms/RBACTimelock.spec.ts +++ b/contracts/tests/mcms/RBACTimelock.spec.ts @@ -52,7 +52,7 @@ describe('RBACTimelock', () => { const data = { id: crc32('mcms.timelock.test-sandbox'), // unique ID for this instance minDelay: minDelay, - rbac: ac.builder.data.contractData.encode(rbacStorage), + rbac: ac.builder.data.contractData.encode(rbacStorage).asCell(), } timelock = blockchain.openContract(rbactl.ContractClient.newFrom(data, code)) @@ -103,7 +103,7 @@ describe('RBACTimelock', () => { }) it('should deploy', async () => { - const body = rbactl.builder.message.in.topUp.encode({ queryId: 1n }) + const body = rbactl.builder.message.in.topUp.encode({ queryId: 1n }).asCell() // Acts as a deploy const result = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) @@ -139,7 +139,7 @@ describe('RBACTimelock', () => { const r = await timelock.sendInternal( deployer.getSender(), toNano('0.1'), - rbactl.builder.message.in.topUp.encode({ queryId: 1n }), + rbactl.builder.message.in.topUp.encode({ queryId: 1n }).asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -154,11 +154,13 @@ describe('RBACTimelock', () => { it('successfully parsed AccessControll opcode', async () => { await deployTopUpTimelock() - const body = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.proposer, - account: other.address, - }) + const body = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.proposer, + account: other.address, + }) + .asCell() const result = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) expect(result.transactions).toHaveTransaction({ @@ -185,11 +187,13 @@ describe('RBACTimelock', () => { it('successful update account - add admin account', async () => { await deployTopUpTimelock() - const body = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.admin, - account: other.address, - }) + const body = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.admin, + account: other.address, + }) + .asCell() const r = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) expect(r.transactions).toHaveTransaction({ @@ -205,11 +209,13 @@ describe('RBACTimelock', () => { it('successful update account - add proposer account', async () => { await deployTopUpTimelock() - const body = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.proposer, - account: other.address, - }) + const body = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.proposer, + account: other.address, + }) + .asCell() const r = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) expect(r.transactions).toHaveTransaction({ @@ -225,11 +231,13 @@ describe('RBACTimelock', () => { it('successful update account - add canceller account', async () => { await deployTopUpTimelock() - const body = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.canceller, - account: other.address, - }) + const body = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.canceller, + account: other.address, + }) + .asCell() const r = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) expect(r.transactions).toHaveTransaction({ @@ -245,11 +253,13 @@ describe('RBACTimelock', () => { it('successful update account - add executor account', async () => { await deployTopUpTimelock() - const body = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.executor, - account: other.address, - }) + const body = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.executor, + account: other.address, + }) + .asCell() const r = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) expect(r.transactions).toHaveTransaction({ @@ -263,19 +273,23 @@ describe('RBACTimelock', () => { }) it('successful update account - remove admin account', async () => { - const bodyInit = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.admin, - account: deployer.address, - }) + const bodyInit = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.admin, + account: deployer.address, + }) + .asCell() await timelock.sendInternal(deployer.getSender(), toNano('0.05'), bodyInit) expect(await acContract.getHasRole(rbactl.roles.admin, deployer.address)).toEqual(true) - const body = ac.builder.message.in.revokeRole.encode({ - queryId: 1n, - role: rbactl.roles.admin, - account: deployer.address, - }) + const body = ac.builder.message.in.revokeRole + .encode({ + queryId: 1n, + role: rbactl.roles.admin, + account: deployer.address, + }) + .asCell() const result = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) expect(result.transactions).toHaveTransaction({ @@ -289,19 +303,23 @@ describe('RBACTimelock', () => { }) it('successful update account - remove proposer account', async () => { - const bodyInit = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.proposer, - account: deployer.address, - }) + const bodyInit = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.proposer, + account: deployer.address, + }) + .asCell() await timelock.sendInternal(deployer.getSender(), toNano('0.05'), bodyInit) expect(await acContract.getHasRole(rbactl.roles.proposer, deployer.address)).toEqual(true) - const body = ac.builder.message.in.revokeRole.encode({ - queryId: 1n, - role: rbactl.roles.proposer, - account: deployer.address, - }) + const body = ac.builder.message.in.revokeRole + .encode({ + queryId: 1n, + role: rbactl.roles.proposer, + account: deployer.address, + }) + .asCell() const result = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) expect(result.transactions).toHaveTransaction({ @@ -315,19 +333,23 @@ describe('RBACTimelock', () => { }) it('successful update account - remove canceller account', async () => { - const bodyInit = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.canceller, - account: deployer.address, - }) + const bodyInit = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.canceller, + account: deployer.address, + }) + .asCell() await timelock.sendInternal(deployer.getSender(), toNano('0.05'), bodyInit) expect(await acContract.getHasRole(rbactl.roles.canceller, deployer.address)).toEqual(true) - const body = ac.builder.message.in.revokeRole.encode({ - queryId: 1n, - role: rbactl.roles.canceller, - account: deployer.address, - }) + const body = ac.builder.message.in.revokeRole + .encode({ + queryId: 1n, + role: rbactl.roles.canceller, + account: deployer.address, + }) + .asCell() const result = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) expect(result.transactions).toHaveTransaction({ @@ -341,19 +363,23 @@ describe('RBACTimelock', () => { }) it('successful update account - remove executor account', async () => { - const bodyInit = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.executor, - account: deployer.address, - }) + const bodyInit = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.executor, + account: deployer.address, + }) + .asCell() await timelock.sendInternal(deployer.getSender(), toNano('0.05'), bodyInit) expect(await acContract.getHasRole(rbactl.roles.executor, deployer.address)).toEqual(true) - const body = ac.builder.message.in.revokeRole.encode({ - queryId: 1n, - role: rbactl.roles.executor, - account: deployer.address, - }) + const body = ac.builder.message.in.revokeRole + .encode({ + queryId: 1n, + role: rbactl.roles.executor, + account: deployer.address, + }) + .asCell() const result = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), body) expect(result.transactions).toHaveTransaction({ @@ -367,11 +393,13 @@ describe('RBACTimelock', () => { }) it('invalid sender for update accounts: wrong_op', async () => { - const bodyInit = ac.builder.message.in.grantRole.encode({ - queryId: 1n, - role: rbactl.roles.admin, - account: other.address, - }) + const bodyInit = ac.builder.message.in.grantRole + .encode({ + queryId: 1n, + role: rbactl.roles.admin, + account: other.address, + }) + .asCell() const result = await timelock.sendInternal(other.getSender(), toNano('0.05'), bodyInit) expect(result.transactions).toHaveTransaction({ @@ -386,7 +414,9 @@ describe('RBACTimelock', () => { it('successful update delay', async () => { const delay = 100 - const bodyInit = rbactl.builder.message.in.updateDelay.encode({ queryId: 1n, newDelay: delay }) + const bodyInit = rbactl.builder.message.in.updateDelay + .encode({ queryId: 1n, newDelay: delay }) + .asCell() const result = await timelock.sendInternal(deployer.getSender(), toNano('0.05'), bodyInit) expect(result.transactions).toHaveTransaction({ @@ -400,7 +430,9 @@ describe('RBACTimelock', () => { }) it('invalid sender for update delay: wrong_op', async () => { - const bodyInit = rbactl.builder.message.in.updateDelay.encode({ queryId: 1n, newDelay: 100 }) + const bodyInit = rbactl.builder.message.in.updateDelay + .encode({ queryId: 1n, newDelay: 100 }) + .asCell() const result = await timelock.sendInternal(other.getSender(), toNano('0.05'), bodyInit) expect(result.transactions).toHaveTransaction({ @@ -427,7 +459,7 @@ describe('RBACTimelock', () => { }, ] const op = { - calls: asSnakeData(calls, (c) => rbactl.builder.data.call.encode(c).asBuilder()), + calls: asSnakeData(calls, (c) => rbactl.builder.data.call.encode(c)), predecessor: predecessor, salt: salt, } @@ -447,7 +479,7 @@ describe('RBACTimelock', () => { op: rbactl.opcodes.in.ScheduleBatch, }) - const offchainId = rbactl.builder.data.operationBatch.encode(op).hash() + const offchainId = rbactl.builder.data.operationBatch.encode(op).asCell().hash() // Verify off-chain and on-chain ID equivalence const id = await timelock.getHashOperationBatch(op) diff --git a/contracts/tests/mcms/RBACTimelockBlockFunction.spec.ts b/contracts/tests/mcms/RBACTimelockBlockFunction.spec.ts index a8c386ae9..196e823f5 100644 --- a/contracts/tests/mcms/RBACTimelockBlockFunction.spec.ts +++ b/contracts/tests/mcms/RBACTimelockBlockFunction.spec.ts @@ -50,7 +50,7 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) @@ -123,7 +123,7 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) @@ -336,7 +336,7 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) @@ -410,7 +410,7 @@ describe('MCMS - RBACTimelockBlockFunctionTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) diff --git a/contracts/tests/mcms/RBACTimelockCancel.spec.ts b/contracts/tests/mcms/RBACTimelockCancel.spec.ts index 827c5619c..71b390d21 100644 --- a/contracts/tests/mcms/RBACTimelockCancel.spec.ts +++ b/contracts/tests/mcms/RBACTimelockCancel.spec.ts @@ -24,10 +24,12 @@ describe('MCMS - RBACTimelockCancelTest', () => { it('should fail if non-canceller tries to cancel', async () => { // Try to cancel with executor role (should fail) - const body = rbactl.builder.message.in.cancel.encode({ - queryId: 1n, - id: BaseTestSetup.EMPTY_SALT, - }) + const body = rbactl.builder.message.in.cancel + .encode({ + queryId: 1n, + id: BaseTestSetup.EMPTY_SALT, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.executorOne.getSender(), @@ -46,19 +48,21 @@ describe('MCMS - RBACTimelockCancelTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation { - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -78,12 +82,14 @@ describe('MCMS - RBACTimelockCancelTest', () => { // Execute operation { - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.executorOne.getSender(), @@ -111,10 +117,12 @@ describe('MCMS - RBACTimelockCancelTest', () => { // Try to cancel finished operation (should fail) { - const cancelBody = rbactl.builder.message.in.cancel.encode({ - queryId: 1n, - id: operationID, - }) + const cancelBody = rbactl.builder.message.in.cancel + .encode({ + queryId: 1n, + id: operationID, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.cancellerOne.getSender(), @@ -142,18 +150,20 @@ describe('MCMS - RBACTimelockCancelTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const scheduleResult = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -180,10 +190,12 @@ describe('MCMS - RBACTimelockCancelTest', () => { expect(await baseTest.bind.timelock.isOperation(operationId)).toBe(true) // Cancel operation - const cancelBody = rbactl.builder.message.in.cancel.encode({ - queryId: 1n, - id: operationId, - }) + const cancelBody = rbactl.builder.message.in.cancel + .encode({ + queryId: 1n, + id: operationId, + }) + .asCell() const cancelResult = await baseTest.bind.timelock.sendInternal( canceller.getSender(), @@ -208,7 +220,9 @@ describe('MCMS - RBACTimelockCancelTest', () => { const cancelMsg = cancelTx[0].inMessage! const opcode = cancelMsg.body.beginParse().preloadUint(32) - const canceledConfirmation = rbactl.builder.message.out.canceled.load(cancelMsg.body) + const canceledConfirmation = rbactl.builder.message.out.canceled.load( + cancelMsg.body.beginParse(), + ) expect(opcode.toString(16)).toEqual(rbactl.opcodes.out.Canceled.toString(16)) expect(canceledConfirmation.queryId).toEqual(1) diff --git a/contracts/tests/mcms/RBACTimelockExecute.spec.ts b/contracts/tests/mcms/RBACTimelockExecute.spec.ts index 4870197b9..46c3e7497 100644 --- a/contracts/tests/mcms/RBACTimelockExecute.spec.ts +++ b/contracts/tests/mcms/RBACTimelockExecute.spec.ts @@ -53,13 +53,15 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const calls = BaseTestSetup.singletonCalls({ target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), }) - const body = rbactl.builder.message.in.bypasserExecuteBatch.encode({ - queryId: 1n, - calls, - }) + const body = rbactl.builder.message.in.bypasserExecuteBatch + .encode({ + queryId: 1n, + calls, + }) + .asCell() // Try with proposer role (should fail) const result = await baseTest.bind.timelock.sendInternal( @@ -86,10 +88,12 @@ describe('MCMS - RBACTimelockExecuteTest', () => { } const calls = BaseTestSetup.singletonCalls(invalidCall) - const body = rbactl.builder.message.in.bypasserExecuteBatch.encode({ - queryId: 1n, - calls, - }) + const body = rbactl.builder.message.in.bypasserExecuteBatch + .encode({ + queryId: 1n, + calls, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.admin.getSender(), @@ -118,25 +122,29 @@ describe('MCMS - RBACTimelockExecuteTest', () => { // Increment counter target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), }, { // Set counterTwo target: counterTwo.address, value: toNano('0.05'), - data: counter.builder.message.in.setCount.encode({ - queryId: 1n, - newCount: 10, - }), + data: counter.builder.message.in.setCount + .encode({ + queryId: 1n, + newCount: 10, + }) + .asCell(), }, ] const encodedCalls = asSnakeData(calls, (c) => - rbactl.builder.data.call.encode(c).asBuilder(), + rbactl.builder.data.call.encode(c).asCell().asBuilder(), ) - const executeMsg = rbactl.builder.message.in.bypasserExecuteBatch.encode({ - queryId: 1n, - calls: encodedCalls, - }) + const executeMsg = rbactl.builder.message.in.bypasserExecuteBatch + .encode({ + queryId: 1n, + calls: encodedCalls, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( signer.getSender(), @@ -164,7 +172,7 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const opcode = bypasserExecutedExternal.body.beginParse().preloadUint(32) const bypasserExecutedEvent = rbactl.builder.message.out.bypasserCallExecuted.load( - bypasserExecutedExternal.body, + bypasserExecutedExternal.body.beginParse(), ) expect(opcode.toString(16)).toEqual(rbactl.opcodes.out.BypasserCallExecuted.toString(16)) @@ -204,15 +212,17 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const calls = BaseTestSetup.singletonCalls({ target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), }) - const body = rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const body = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() // Try with proposer role (should fail) const result = await baseTest.bind.timelock.sendInternal( @@ -233,17 +243,19 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const calls = BaseTestSetup.singletonCalls({ target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), }) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const scheduleResult = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -260,12 +272,14 @@ describe('MCMS - RBACTimelockExecuteTest', () => { // Try to execute before delay is met (only advance a short time) baseTest.warpTime(Number(BaseTestSetup.MIN_DELAY - 2n * 24n * 60n * 60n)) // 2 days short - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.executorOne.getSender(), @@ -285,19 +299,21 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const predecessorCall: rbactl.Call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const predecessorCalls = BaseTestSetup.singletonCalls(predecessorCall) { // Schedule predecessor operation - const scheduleCall = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls: predecessorCalls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleCall = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls: predecessorCalls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -318,21 +334,25 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const dependentCall: rbactl.Call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.setCount.encode({ - queryId: 2n, - newCount: 5, - }), + data: counter.builder.message.in.setCount + .encode({ + queryId: 2n, + newCount: 5, + }) + .asCell(), } const dependentCalls = BaseTestSetup.singletonCalls(dependentCall) { - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 2n, - calls: dependentCalls, - predecessor: predecessorId, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 2n, + calls: dependentCalls, + predecessor: predecessorId, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -345,12 +365,14 @@ describe('MCMS - RBACTimelockExecuteTest', () => { baseTest.warpTime(Number(BaseTestSetup.MIN_DELAY + 2n * 24n * 60n * 60n)) // 2 days extra // Try to execute dependent operation (should fail) - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 3n, - calls: dependentCalls, - predecessor: predecessorId, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 3n, + calls: dependentCalls, + predecessor: predecessorId, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.executorOne.getSender(), @@ -377,13 +399,15 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const calls = BaseTestSetup.singletonCalls(invalidCall) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -395,12 +419,14 @@ describe('MCMS - RBACTimelockExecuteTest', () => { baseTest.warpTime(Number(BaseTestSetup.MIN_DELAY + 2n * 24n * 60n * 60n)) // Try to execute (should fail due to invalid call) - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 2n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 2n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.executorOne.getSender(), @@ -427,21 +453,25 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const setCountCall: rbactl.Call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.setCount.encode({ - queryId: 1n, - newCount: 10, - }), + data: counter.builder.message.in.setCount + .encode({ + queryId: 1n, + newCount: 10, + }) + .asCell(), } const calls = BaseTestSetup.singletonCalls(setCountCall) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -453,12 +483,14 @@ describe('MCMS - RBACTimelockExecuteTest', () => { baseTest.warpTime(Number(BaseTestSetup.MIN_DELAY + 1n)) // Execute operation - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 2n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 2n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( executor.getSender(), @@ -486,7 +518,7 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const opcode = callExecutedExternal.body.beginParse().preloadUint(32) const callExecutedEvent = rbactl.builder.message.out.callExecuted.load( - callExecutedExternal.body, + callExecutedExternal.body.beginParse(), ) expect(opcode.toString(16)).toEqual(rbactl.opcodes.out.CallExecuted.toString(16)) @@ -519,18 +551,20 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const incrementCall: rbactl.Call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(incrementCall) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -545,12 +579,14 @@ describe('MCMS - RBACTimelockExecuteTest', () => { await baseTest.grantCallProxyExecutorRole() // Execute through call proxy using external caller - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 2n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 2n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() // Execute via call proxy const proxyResult = await baseTest.bind.callProxy.sendInternal( @@ -573,18 +609,20 @@ describe('MCMS - RBACTimelockExecuteTest', () => { const incrementCall: rbactl.Call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(incrementCall) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -596,12 +634,14 @@ describe('MCMS - RBACTimelockExecuteTest', () => { baseTest.warpTime(Number(BaseTestSetup.MIN_DELAY + 1n)) // Try to execute through call proxy without granting executor role - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 2n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 2n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() const proxyResult = await baseTest.bind.callProxy.sendInternal( baseTest.acc.deployer.getSender(), diff --git a/contracts/tests/mcms/RBACTimelockGetters.spec.ts b/contracts/tests/mcms/RBACTimelockGetters.spec.ts index db6d5d85a..01e7d3e4e 100644 --- a/contracts/tests/mcms/RBACTimelockGetters.spec.ts +++ b/contracts/tests/mcms/RBACTimelockGetters.spec.ts @@ -32,18 +32,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -74,18 +76,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -108,18 +112,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -130,12 +136,14 @@ describe('MCMS - RBACTimelockGetters', () => { // Wait for delay and execute baseTest.warpTime(Number(BaseTestSetup.MIN_DELAY)) - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.executorOne.getSender(), @@ -166,18 +174,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -203,18 +213,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -240,18 +252,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -277,18 +291,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -299,12 +315,14 @@ describe('MCMS - RBACTimelockGetters', () => { // Wait for delay and execute baseTest.warpTime(Number(BaseTestSetup.MIN_DELAY)) - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.executorOne.getSender(), @@ -335,18 +353,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -369,18 +389,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -391,12 +413,14 @@ describe('MCMS - RBACTimelockGetters', () => { // Wait for delay and execute baseTest.warpTime(Number(BaseTestSetup.MIN_DELAY)) - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.executorOne.getSender(), @@ -427,20 +451,22 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) const scheduleTime = baseTest.blockchain.now!! // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -463,18 +489,20 @@ describe('MCMS - RBACTimelockGetters', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) // Schedule operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -485,12 +513,14 @@ describe('MCMS - RBACTimelockGetters', () => { // Wait for delay and execute baseTest.warpTime(Number(BaseTestSetup.MIN_DELAY)) - const executeBody = rbactl.builder.message.in.executeBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - }) + const executeBody = rbactl.builder.message.in.executeBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.executorOne.getSender(), diff --git a/contracts/tests/mcms/RBACTimelockReceivable.spec.ts b/contracts/tests/mcms/RBACTimelockReceivable.spec.ts index e2fce3c96..53c8fdfd4 100644 --- a/contracts/tests/mcms/RBACTimelockReceivable.spec.ts +++ b/contracts/tests/mcms/RBACTimelockReceivable.spec.ts @@ -24,9 +24,11 @@ describe('MCMS - RBACTimelockReceivable', () => { const contractBefore = await baseTest.blockchain.getContract(baseTest.bind.timelock.address) const balanceBefore = await contractBefore.account.account?.storage.balance! - const topUpBody = rbactl.builder.message.in.topUp.encode({ - queryId: 1n, - }) + const topUpBody = rbactl.builder.message.in.topUp + .encode({ + queryId: 1n, + }) + .asCell() const transferAmount = toNano('0.5') diff --git a/contracts/tests/mcms/RBACTimelockScheduleBatch.spec.ts b/contracts/tests/mcms/RBACTimelockScheduleBatch.spec.ts index 6c9c333ba..8b4b774cb 100644 --- a/contracts/tests/mcms/RBACTimelockScheduleBatch.spec.ts +++ b/contracts/tests/mcms/RBACTimelockScheduleBatch.spec.ts @@ -32,15 +32,15 @@ describe('MCMS - RBACTimelockScheduleBatchTest', () => { { target: baseTest.bind.counter.address, value: 0n, - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), }, { target: baseTest.bind.counter.address, value: 0n, - data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }).asCell(), }, ], - (c) => rbactl.builder.data.call.encode(c).asBuilder(), + (c) => rbactl.builder.data.call.encode(c), ) }) @@ -49,31 +49,33 @@ describe('MCMS - RBACTimelockScheduleBatchTest', () => { { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), }, { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }).asCell(), }, ] return [ - calls.map((call) => rbactl.builder.data.call.encode(call)), - asSnakeData(calls, (c) => rbactl.builder.data.call.encode(c).asBuilder()), + calls.map((call) => rbactl.builder.data.call.encode(call).asCell()), + asSnakeData(calls, (c) => rbactl.builder.data.call.encode(c)), ] } it('should fail if non-proposer tries to schedule batch', async () => { const [callVec, calls] = CreateCallBatch() - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() // Try to schedule with executor role (should fail) const result = await baseTest.bind.timelock.sendInternal( @@ -92,10 +94,12 @@ describe('MCMS - RBACTimelockScheduleBatchTest', () => { it('should fail if batch contains blocked function', async () => { // Block the increment function selector - const blockBody = rbactl.builder.message.in.blockFunctionSelector.encode({ - queryId: 1n, - selector: counter.opcodes.in.IncreaseCount, - }) + const blockBody = rbactl.builder.message.in.blockFunctionSelector + .encode({ + queryId: 1n, + selector: counter.opcodes.in.IncreaseCount, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.admin.getSender(), @@ -106,13 +110,15 @@ describe('MCMS - RBACTimelockScheduleBatchTest', () => { // Try to schedule a batch with the blocked function const [callVec, calls] = CreateCallBatch() - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -151,13 +157,15 @@ describe('MCMS - RBACTimelockScheduleBatchTest', () => { expect(await baseTest.bind.timelock.isOperation(batchedOperationID)).toBe(false) // Schedule the batch operation - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( scheduler.getSender(), @@ -184,7 +192,9 @@ describe('MCMS - RBACTimelockScheduleBatchTest', () => { ) const opcode = result.externals[i].body.beginParse().preloadUint(32) - const callScheduled = rbactl.builder.message.out.callScheduled.load(result.externals[i].body) + const callScheduled = rbactl.builder.message.out.callScheduled.load( + result.externals[i].body.beginParse(), + ) expect(opcode.toString(16)).toEqual(rbactl.opcodes.out.CallScheduled.toString(16)) expect(callScheduled.queryId).toEqual(1) @@ -224,17 +234,19 @@ describe('MCMS - RBACTimelockScheduleTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() // Try to schedule with a non-proposer account (should fail) const result = await baseTest.bind.timelock.sendInternal( @@ -253,10 +265,12 @@ describe('MCMS - RBACTimelockScheduleTest', () => { it('should fail if scheduling a blocked function', async () => { // Block the increment function selector - const blockBody = rbactl.builder.message.in.blockFunctionSelector.encode({ - queryId: 1n, - selector: counter.opcodes.in.IncreaseCount, - }) + const blockBody = rbactl.builder.message.in.blockFunctionSelector + .encode({ + queryId: 1n, + selector: counter.opcodes.in.IncreaseCount, + }) + .asCell() await baseTest.bind.timelock.sendInternal( baseTest.acc.admin.getSender(), @@ -268,17 +282,19 @@ describe('MCMS - RBACTimelockScheduleTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -298,17 +314,19 @@ describe('MCMS - RBACTimelockScheduleTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() // Schedule operation first time const firstResult = await baseTest.bind.timelock.sendInternal( @@ -342,17 +360,19 @@ describe('MCMS - RBACTimelockScheduleTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY - 1n, // Less than minimum delay - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY - 1n, // Less than minimum delay + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( baseTest.acc.proposerOne.getSender(), @@ -380,17 +400,19 @@ describe('MCMS - RBACTimelockScheduleTest', () => { const call = { target: baseTest.bind.counter.address, value: toNano('0.05'), - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), } const calls = BaseTestSetup.singletonCalls(call) - const scheduleBody = rbactl.builder.message.in.scheduleBatch.encode({ - queryId: 1n, - calls, - predecessor: BaseTestSetup.NO_PREDECESSOR, - salt: BaseTestSetup.EMPTY_SALT, - delay: BaseTestSetup.MIN_DELAY, - }) + const scheduleBody = rbactl.builder.message.in.scheduleBatch + .encode({ + queryId: 1n, + calls, + predecessor: BaseTestSetup.NO_PREDECESSOR, + salt: BaseTestSetup.EMPTY_SALT, + delay: BaseTestSetup.MIN_DELAY, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( scheduler.getSender(), diff --git a/contracts/tests/mcms/RBACTimelockUpdateDelay.spec.ts b/contracts/tests/mcms/RBACTimelockUpdateDelay.spec.ts index 3c04ce73f..3298643b1 100644 --- a/contracts/tests/mcms/RBACTimelockUpdateDelay.spec.ts +++ b/contracts/tests/mcms/RBACTimelockUpdateDelay.spec.ts @@ -58,7 +58,7 @@ describe('MCMS - RBACTimelockUpdateDelayTest', () => { const delayChangedMsg = delayChangedTx[0].inMessage! const opcode = delayChangedMsg.body.beginParse().preloadUint(32) const delayChangedConfirmation = rbactl.builder.message.out.minDelayChange.load( - delayChangedMsg.body, + delayChangedMsg.body.beginParse(), ) expect(opcode.toString(16)).toEqual(rbactl.opcodes.out.MinDelayChange.toString(16)) @@ -72,10 +72,12 @@ describe('MCMS - RBACTimelockUpdateDelayTest', () => { }) async function updateTimelockDelay(sender: SandboxContract) { - const body = rbactl.builder.message.in.updateDelay.encode({ - queryId: 1n, - newDelay, - }) + const body = rbactl.builder.message.in.updateDelay + .encode({ + queryId: 1n, + newDelay, + }) + .asCell() const result = await baseTest.bind.timelock.sendInternal( sender.getSender(), diff --git a/contracts/wrappers/jetton/JettonCode.ts b/contracts/wrappers/jetton/JettonCode.ts new file mode 100644 index 000000000..efebf6ea3 --- /dev/null +++ b/contracts/wrappers/jetton/JettonCode.ts @@ -0,0 +1,31 @@ +import { Cell } from '@ton/core' +import { readFileSync } from 'fs' +import { env } from 'process' + +const PATH_CONTRACTS_JETTON = env.PATH_CONTRACTS_JETTON + +export async function JettonMinterCode(): Promise { + const compiledPath = `${PATH_CONTRACTS_JETTON}/JettonMinter.compiled.json` + const compiled = JSON.parse(readFileSync(compiledPath, 'utf8')) + const hex = compiled.hex + if (!hex) { + throw new Error('Compiled JettonMinter code hex not found in JSON') + } + // Remove 0x prefix if present + const hexStr = hex.startsWith('0x') ? hex.slice(2) : hex + const boc = Buffer.from(hexStr, 'hex') + return Cell.fromBoc(boc)[0] +} + +export async function JettonWalletCode(): Promise { + const compiledPath = `${PATH_CONTRACTS_JETTON}/JettonWallet.compiled.json` + const compiled = JSON.parse(readFileSync(compiledPath, 'utf8')) + const hex = compiled.hex + if (!hex) { + throw new Error('Compiled JettonWallet code hex not found in JSON') + } + // Remove 0x prefix if present + const hexStr = hex.startsWith('0x') ? hex.slice(2) : hex + const boc = Buffer.from(hexStr, 'hex') + return Cell.fromBoc(boc)[0] +} diff --git a/contracts/wrappers/jetton/JettonMinter.ts b/contracts/wrappers/jetton/JettonMinter.ts index 4dee95361..ca333628b 100644 --- a/contracts/wrappers/jetton/JettonMinter.ts +++ b/contracts/wrappers/jetton/JettonMinter.ts @@ -111,7 +111,7 @@ export class JettonMinter implements Contract { await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, - body: Cell.EMPTY, + body: beginCell().endCell(), }) } diff --git a/contracts/wrappers/jetton/JettonWallet.ts b/contracts/wrappers/jetton/JettonWallet.ts index 83613482c..469053cbb 100644 --- a/contracts/wrappers/jetton/JettonWallet.ts +++ b/contracts/wrappers/jetton/JettonWallet.ts @@ -85,7 +85,7 @@ export class JettonWallet implements Contract { await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, - body: Cell.EMPTY, + body: beginCell().endCell(), }) } From 0a3674a683eb9b1d96ea3a28efddfc5ddb572a74 Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Wed, 10 Sep 2025 15:59:13 -0300 Subject: [PATCH 4/8] fix: add missing .asCell() --- contracts/tests/mcms/MCMS.spec.ts | 2 +- ...anyChainMultiSigExecuteErrorOracle.spec.ts | 128 ++++++++++-------- .../tests/mcms/RBACTimelockHashing.spec.ts | 8 +- 3 files changed, 78 insertions(+), 60 deletions(-) diff --git a/contracts/tests/mcms/MCMS.spec.ts b/contracts/tests/mcms/MCMS.spec.ts index 44e75dfd7..58edb0904 100644 --- a/contracts/tests/mcms/MCMS.spec.ts +++ b/contracts/tests/mcms/MCMS.spec.ts @@ -71,7 +71,7 @@ describe('MCMS', () => { it('should deploy', async () => { // Check that MCMS contract is deployed - const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }) + const body = mcms.builder.message.in.topUp.encode({ queryId: 1n }).asCell() const result = await bind.mcms.sendInternal(acc.deployer.getSender(), toNano('0.05'), body) expect(result.transactions).toHaveTransaction({ diff --git a/contracts/tests/mcms/ManyChainMultiSigExecuteErrorOracle.spec.ts b/contracts/tests/mcms/ManyChainMultiSigExecuteErrorOracle.spec.ts index 6cd67d7b9..f9b14cdf1 100644 --- a/contracts/tests/mcms/ManyChainMultiSigExecuteErrorOracle.spec.ts +++ b/contracts/tests/mcms/ManyChainMultiSigExecuteErrorOracle.spec.ts @@ -31,10 +31,12 @@ describe('MCMS - ManyChainMultiSigExecuteErrorOracleTest', () => { const r = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), toNano('1'), - mcms.builder.message.in.transferOracleRole.encode({ - queryId: 1n, - newOracle: acc.oracle.address, - }), + mcms.builder.message.in.transferOracleRole + .encode({ + queryId: 1n, + newOracle: acc.oracle.address, + }) + .asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -58,10 +60,12 @@ describe('MCMS - ManyChainMultiSigExecuteErrorOracleTest', () => { const r = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), toNano('1'), - mcms.builder.message.in.transferOracleRole.encode({ - queryId: 1n, - newOracle: acc.oracle.address, - }), + mcms.builder.message.in.transferOracleRole + .encode({ + queryId: 1n, + newOracle: acc.oracle.address, + }) + .asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -80,11 +84,13 @@ describe('MCMS - ManyChainMultiSigExecuteErrorOracleTest', () => { // Execute first operation const proof1 = baseTest.getProofForOp(0) - const execBody = mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(baseTest.testOps[0]), - proof: proof1, - }) + const execBody = mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(baseTest.testOps[0]).asCell(), + proof: proof1, + }) + .asCell() const r1 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -108,14 +114,16 @@ describe('MCMS - ManyChainMultiSigExecuteErrorOracleTest', () => { const r2 = await baseTest.bind.mcms.sendInternal( acc.oracle.getSender(), toNano('1'), - mcms.builder.message.in.submitErrorReport.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(baseTest.testOps[0]), - proof: proof1, - opTxHash: txHash, - errorTxHash: txHash, - errorCode: 1337, - }), + mcms.builder.message.in.submitErrorReport + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(baseTest.testOps[0]).asCell(), + proof: proof1, + opTxHash: txHash, + errorTxHash: txHash, + errorCode: 1337, + }) + .asCell(), ) expect(r2.transactions).toHaveTransaction({ @@ -145,11 +153,13 @@ describe('MCMS - ManyChainMultiSigExecuteErrorOracleTest', () => { // Execute first operation const proof1 = baseTest.getProofForOp(0) - const execBody = mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(baseTest.testOps[0]), - proof: proof1, - }) + const execBody = mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(baseTest.testOps[0]).asCell(), + proof: proof1, + }) + .asCell() const r1 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -173,14 +183,16 @@ describe('MCMS - ManyChainMultiSigExecuteErrorOracleTest', () => { const r2 = await baseTest.bind.mcms.sendInternal( acc.oracle.getSender(), toNano('1'), - mcms.builder.message.in.submitErrorReport.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(baseTest.testOps[0]), - proof: proof1, - opTxHash: txHash, - errorTxHash: txHash, - errorCode: 1337, - }), + mcms.builder.message.in.submitErrorReport + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(baseTest.testOps[0]).asCell(), + proof: proof1, + opTxHash: txHash, + errorTxHash: txHash, + errorCode: 1337, + }) + .asCell(), ) expect(r2.transactions).toHaveTransaction({ @@ -208,11 +220,13 @@ describe('MCMS - ManyChainMultiSigExecuteErrorOracleTest', () => { // Try to execute the new op, new root const proofNew = baseTest.getProofForOp(0) - const execBodyNew = mcms.builder.message.in.execute.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(baseTest.testOps[0]), - proof: proofNew, - }) + const execBodyNew = mcms.builder.message.in.execute + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(baseTest.testOps[0]).asCell(), + proof: proofNew, + }) + .asCell() const r3 = await baseTest.bind.mcms.sendInternal( baseTest.acc.deployer.getSender(), @@ -237,14 +251,16 @@ describe('MCMS - ManyChainMultiSigExecuteErrorOracleTest', () => { const r = await baseTest.bind.mcms.sendInternal( baseTest.acc.multisigOwner.getSender(), // not an oracle toNano('1'), - mcms.builder.message.in.submitErrorReport.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(baseTest.testOps[0]), - proof, - opTxHash: txHash, - errorTxHash: txHash, - errorCode: 1337, - }), + mcms.builder.message.in.submitErrorReport + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(baseTest.testOps[0]).asCell(), + proof, + opTxHash: txHash, + errorTxHash: txHash, + errorCode: 1337, + }) + .asCell(), ) expect(r.transactions).toHaveTransaction({ @@ -265,14 +281,16 @@ describe('MCMS - ManyChainMultiSigExecuteErrorOracleTest', () => { const r = await baseTest.bind.mcms.sendInternal( acc.oracle.getSender(), toNano('1'), - mcms.builder.message.in.submitErrorReport.encode({ - queryId: 1n, - op: mcms.builder.data.op.encode(baseTest.testOps[0]), - proof: invalidProof, - opTxHash: txHash, - errorTxHash: txHash, - errorCode: 1337, - }), + mcms.builder.message.in.submitErrorReport + .encode({ + queryId: 1n, + op: mcms.builder.data.op.encode(baseTest.testOps[0]).asCell(), + proof: invalidProof, + opTxHash: txHash, + errorTxHash: txHash, + errorCode: 1337, + }) + .asCell(), ) expect(r.transactions).toHaveTransaction({ diff --git a/contracts/tests/mcms/RBACTimelockHashing.spec.ts b/contracts/tests/mcms/RBACTimelockHashing.spec.ts index d5f07d583..71c0351f0 100644 --- a/contracts/tests/mcms/RBACTimelockHashing.spec.ts +++ b/contracts/tests/mcms/RBACTimelockHashing.spec.ts @@ -27,15 +27,15 @@ describe('MCMS - RBACTimelockHashingTest', () => { { target: baseTest.bind.counter.address, value: 0n, - data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 1n }).asCell(), }, { target: baseTest.bind.counter.address, value: 0n, - data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }), + data: counter.builder.message.in.increaseCount.encode({ queryId: 2n }).asCell(), }, ], - (c) => rbactl.builder.data.call.encode(c).asBuilder(), + (c) => rbactl.builder.data.call.encode(c), ) // Schedule operation @@ -55,7 +55,7 @@ describe('MCMS - RBACTimelockHashingTest', () => { } const hashedOperation = await baseTest.bind.timelock.getHashOperationBatch(operationBatch) - const offchainId = rbactl.builder.data.operationBatch.encode(operationBatch).hash() + const offchainId = rbactl.builder.data.operationBatch.encode(operationBatch).asCell().hash() const expectedHash = BigInt('0x' + offchainId.toString('hex')) expect(hashedOperation).toEqual(expectedHash) From 274233a5537cce9e53b357a26aa90724110060bf Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Thu, 11 Sep 2025 10:34:40 -0300 Subject: [PATCH 5/8] feat: debugging traces in TS --- contracts/src/utils/index.ts | 1 + contracts/src/utils/prettyPrint.ts | 320 +++++++++++++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 contracts/src/utils/prettyPrint.ts diff --git a/contracts/src/utils/index.ts b/contracts/src/utils/index.ts index c9f6f047d..adaa6c2d9 100644 --- a/contracts/src/utils/index.ts +++ b/contracts/src/utils/index.ts @@ -1 +1,2 @@ export * from './types' +export * from './prettyPrint' diff --git a/contracts/src/utils/prettyPrint.ts b/contracts/src/utils/prettyPrint.ts new file mode 100644 index 000000000..bb9990813 --- /dev/null +++ b/contracts/src/utils/prettyPrint.ts @@ -0,0 +1,320 @@ +import { + Address, + Cell, + CommonMessageInfoExternalIn, + CommonMessageInfoExternalOut, + CommonMessageInfoInternal, + Message, +} from '@ton/core' +import { BlockchainTransaction } from '@ton/sandbox' +import { prettifyTransaction, PrettyTransaction } from '@ton/test-utils' + +/** + * Exit code type - represents TVM exit codes + */ +export type ExitCode = number + +/** + * Exit code descriptions for better readability. + */ +const EXIT_CODE_DESCRIPTIONS: Record = { + 0: 'Success', + 1: 'Alternative success', + 2: 'Stack underflow', + 3: 'Stack overflow', + 4: 'Integer overflow', + 5: 'Integer out of expected range', + 6: 'Invalid opcode', + 7: 'Type check error', + 8: 'Cell overflow', + 9: 'Cell underflow', + 10: 'Dictionary error', + 11: 'Unknown error', + 12: 'Fatal error', + 13: 'Out of gas', + 14: 'Virtualization error', + // Add more as needed +} + +export async function dump(txs: BlockchainTransaction[]): Promise { + return dumpRecursive(txs[0], txs) +} + +/** + * Describes an exit code with human-readable information. + */ +function describeExitCode(exitCode?: ExitCode): string { + if (exitCode === undefined || exitCode === null) { + return 'pending' + } + + if (exitCode === 0) { + return 'exit code 0' + } + + const description = EXIT_CODE_DESCRIPTIONS[exitCode] || 'Unknown error' + return `exit code: ${exitCode} (${description})` +} + +/** + * Describes the body/payload of a message cell. + */ +function describeBody(body: Cell): string { + try { + const slice = body.beginParse() + + if (slice.remainingBits === 0) { + return 'empty' + } + + // Try to parse as opcode (first 32 bits) + if (slice.remainingBits >= 32) { + try { + const opcode = slice.loadUint(32) + return `opcode: 0x${opcode.toString(16).padStart(8, '0')}` + } catch { + // Fall through to string parsing + } + } + + // Try to parse as string snake + try { + const strSnake = body.beginParse().loadStringTail() + if (strSnake) { + return `stringSnake: ${strSnake}` + } + } catch { + // Fall through to hex dump + } + + // Fall back to hex representation + return `body: ${body.toBoc().toString('hex').substring(0, 32)}...` + } catch (error) { + return `body: parse error - ${error}` + } +} + +/** + * Describes the body of an emitted external message. + */ +function describeEmitBody(body: Cell): string { + try { + const slice = body.beginParse() + + if (slice.remainingBits === 0) { + return 'empty' + } + + // Try string snake first for external messages (events) + try { + const strSnake = body.beginParse().loadStringTail() + if (strSnake) { + return `stringSnake: ${strSnake}` + } + } catch { + // Fall through + } + + // Try opcode + if (slice.remainingBits >= 32) { + try { + const opcode = slice.loadUint(32) + return `opcode: ${opcode.toString(16)}` + } catch { + // Fall through + } + } + + return `body: ${body.toBoc().toString('hex').substring(0, 32)}...` + } catch (error) { + return `body: parse error - ${error}` + } +} + +/** + * Describes an internal message with amount, bounce status, and exit code. + */ +async function describeInternalMessage( + info: CommonMessageInfoInternal, + body: Cell, + prettyTx: PrettyTransaction, + exitCode?: ExitCode, +): Promise { + let description = describeBody(body) + + // Add amount information + if (info.type === 'internal') { + description += `, amount: ${info.value.coins.toString()}` + + if (info.bounced) { + description += ', bounced' + } + } + + description += ', ' + describeExitCode(exitCode) + + const srcAddr = contractNameFromPrettyAddress(prettyTx.from) || 'external' + const dstAddr = contractNameFromPrettyAddress(prettyTx.to) || 'unknown' + + return `${srcAddr} -- (${description}) --> ${dstAddr}` +} + +function contractNameFromPrettyAddress(address: string | undefined): string | undefined { + if (!address) return undefined + const parts = address.split('(') + if (parts.length > 1) { + return parts[1].trim().replace(')', '') + } + return undefined +} + +/** + * Describes an external incoming message. + */ +async function describeExternalInMessage( + info: CommonMessageInfoExternalIn, + body: Cell, + prettyTx: PrettyTransaction, + exitCode?: ExitCode, +): Promise { + const description = describeBody(body) + ', ' + describeExitCode(exitCode) + const srcAddr = contractNameFromPrettyAddress(prettyTx.from) || 'external' + const dstAddr = contractNameFromPrettyAddress(prettyTx.to) || 'unknown' + + return `${srcAddr} -- (${description}) --> ${dstAddr}` +} + +/** + * Describes an external outgoing message (event). + */ +function describeExternalOutMessage( + src: string, + info: CommonMessageInfoExternalOut, + body: Cell, +): string { + const description = describeEmitBody(body) + return `${src} emit: (${description})` +} + +/** + * Recursively dumps a received message and its outgoing messages. + * This is a helper function for the main dump method. + */ +async function dumpRecursive( + tx: BlockchainTransaction, + txs: BlockchainTransaction[], +): Promise { + const output: string[] = [] + let prettyTx = prettifyTransaction(tx) + + // Describe the main message + const message = tx.inMessage + if (message != null) { + let exitCode: number | undefined + if (tx.description.type === 'generic' && tx.description.computePhase.type === 'vm') { + exitCode = tx.description.computePhase.exitCode + } + + switch (message.info.type) { + case 'internal': + output.push(await describeInternalMessage(message.info, message.body, prettyTx, exitCode)) + break + case 'external-in': + output.push(await describeExternalInMessage(message.info, message.body, prettyTx, exitCode)) + break + case 'external-out': + throw `external-out message don't have a tx` + default: + throw `unknown message type` + } + } + + // Add outgoing received messages (with full traces) + for (const [_, outMsg] of tx.outMessages) { + if (outMsg === null || outMsg === undefined) continue + const foundTx = txs.find( + (t) => t.inMessage != null && t.inMessage != undefined && compareMsgs(t.inMessage, outMsg), + ) + if (foundTx) { + const lines = await dumpRecursive(foundTx, txs) + for (let i = 0; i < lines.length; i++) { + if (i === 0) { + output.push('└ ' + lines[i]) + } else { + output.push('│ ' + lines[i]) + } + } + } else if (outMsg.info.type === 'external-out') { + output.push( + '└ ' + + describeExternalOutMessage( + contractNameFromPrettyAddress(prettyTx.to)!, + outMsg.info, + outMsg.body, + ), + ) + } + } + + return output +} + +function compareMsgs(inMessage: Message, outMsg: Message): boolean { + if (inMessage.info.type == 'internal' && outMsg.info.type == 'internal') { + return ( + inMessage.info.src.equals(outMsg.info.src) && + inMessage.info.dest.equals(outMsg.info.dest) && + inMessage.info.createdLt === outMsg.info.createdLt + ) + } else if (inMessage.info.type == 'external-in' && outMsg.info.type == 'external-in') { + return inMessage.info.dest.equals(outMsg.info.dest) + } + return false +} + +/** + * Utility to format addresses in a consistent way. + */ +export function formatAddress(address: Address | string | undefined | null): string { + if (!address) return 'NONE' + if (typeof address === 'string') return address + return address.toString() +} + +/** + * Utility to format amounts in a human-readable way. + */ +export function formatAmount(amount: bigint, decimals: number = 9): string { + const divisor = BigInt(10 ** decimals) + const wholePart = amount / divisor + const fractionalPart = amount % divisor + + if (fractionalPart === 0n) { + return wholePart.toString() + } + + return `${wholePart}.${fractionalPart.toString().padStart(decimals, '0').replace(/0+$/, '')}` +} + +export function prettifyAddressesMap(transactions: BlockchainTransaction[]): Map { + const map = new Map() + for (const tx of transactions) { + if (!tx.inMessage) continue + const prettyTx = prettifyTransaction(tx) + if ( + tx.inMessage.info.src != null && + tx.inMessage.info.src != undefined && + tx.inMessage.info.src instanceof Address + ) { + map.set(tx.inMessage.info.src.toRawString(), contractNameFromPrettyAddress(prettyTx.from)!) + } + if ( + tx.inMessage.info.dest != null && + tx.inMessage.info.dest != undefined && + tx.inMessage.info.dest instanceof Address + ) { + map.set(tx.inMessage.info.dest.toRawString(), contractNameFromPrettyAddress(prettyTx.to)!) + } + } + return map +} From 44b79bd5559e4a092f45e097aa39a161c372b8bf Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Thu, 11 Sep 2025 10:39:58 -0300 Subject: [PATCH 6/8] test: show more descriptive address difference --- contracts/tests/Logs.ts | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/contracts/tests/Logs.ts b/contracts/tests/Logs.ts index 7edcc3fbe..75a38a58f 100644 --- a/contracts/tests/Logs.ts +++ b/contracts/tests/Logs.ts @@ -5,6 +5,7 @@ import * as OCR3Logs from '../wrappers/libraries/ocr/Logs' import * as ReceiverLogs from '../wrappers/examples/ccip/Logs' import { fromSnakeData } from '../src/utils/types' import { merkleRootsFromCell, priceUpdatesFromCell } from '../wrappers/ccip/OffRamp' +import { prettifyAddressesMap } from '../src/utils' // https://github.com/ton-blockchain/liquid-staking-contract/blob/1f4e9badbed52a4cf80cc58e4bb36ed375c6c8e7/utils.ts#L269-L294 export const getExternals = (transactions: BlockchainTransaction[]) => { @@ -69,11 +70,12 @@ type Handler = ( message: Message, from: Address, match: LogTypeMap[T], + addressesMap: Map, ) => boolean const handlers: { [K in CombinedLogType]: Handler } = { - [CCIPLogs.LogTypes.CCIPMessageSent]: (x, from, match) => - testLogCCIPMessageSent(x, from, match as DeepPartial), + [CCIPLogs.LogTypes.CCIPMessageSent]: (x, from, match, addressesMap) => + testLogCCIPMessageSent(x, from, match as DeepPartial, addressesMap), [CCIPLogs.LogTypes.CCIPCommitReportAccepted]: (x, from, match) => testLogCCIPCommitReportAccepted( @@ -102,7 +104,10 @@ export const assertLog = ( type: T, match: LogMatch, ) => { - const matched = getExternals(transactions).some((x) => handlers[type](x, from, match)) + const prettyAddressesMap = prettifyAddressesMap(transactions) + const matched = getExternals(transactions).some((x) => + handlers[type](x, from, match, prettyAddressesMap), + ) expect(matched).toBe(true) } @@ -133,6 +138,7 @@ export const testLogCCIPMessageSent = ( message: Message, from: Address, match: DeepPartial, + prettyAddressesMap: Map, ) => { return testLog(message, from, CCIPLogs.LogTypes.CCIPMessageSent, (x) => { let bs = x.beginParse() @@ -162,7 +168,23 @@ export const testLogCCIPMessageSent = ( }, } - return matchesObject(msg, match) + // Check sender address using .equals() if specified in match + if (match.message?.sender && match.message.sender instanceof Address) { + if (!sender.equals(match.message.sender)) { + throw new Error( + `Sender address mismatch:\n` + + ` Expected: ${match.message.sender.toString()} (${prettyAddressesMap.get(match.message.sender.toRawString())})\n` + + ` Received: ${sender.toString()} (${prettyAddressesMap.get(sender.toRawString())})`, + ) + } + } + + // Check other fields using toMatchObject (excluding sender to avoid object comparison) + const { sender: _, ...messageWithoutSender } = msg.message + const { sender: __, ...matchWithoutSender } = match.message || {} + + expect({ message: messageWithoutSender }).toMatchObject({ message: matchWithoutSender }) + return true }) } From 89aeb19730d9036175ceeb130af4ed8f99b34bc8 Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Thu, 11 Sep 2025 16:01:11 -0300 Subject: [PATCH 7/8] test: token transfer onramp e2e test --- contracts/contracts/ccip/types.tolk | 4 +- contracts/contracts/lib/utils.tolk | 64 ++++- contracts/tests/Jetton.spec.ts | 29 +-- contracts/tests/ccip/CCIPRouter.spec.ts | 219 +++++++++++++++++- .../wrappers/examples/jetton/JettonSender.ts | 4 +- .../wrappers/examples/jetton/OnrampMock.ts | 4 +- .../examples/jetton/SimpleJettonReceiver.ts | 14 +- contracts/wrappers/examples/jetton/types.ts | 23 +- contracts/wrappers/jetton/JettonMinter.ts | 2 +- contracts/wrappers/jetton/JettonWallet.ts | 2 +- 10 files changed, 314 insertions(+), 51 deletions(-) diff --git a/contracts/contracts/ccip/types.tolk b/contracts/contracts/ccip/types.tolk index dfb7ecb1d..af2b9d381 100644 --- a/contracts/contracts/ccip/types.tolk +++ b/contracts/contracts/ccip/types.tolk @@ -122,7 +122,7 @@ fun TVM2AnyRampMessage.generateMessageId(self, metadataHash: uint256): uint256 { // Router struct TokenAmount { - amount: uint256; + amount: coins; token: address; } @@ -133,6 +133,8 @@ struct (0x10000001) SetRamp { onRamp: address; } +// TODO should separate CCIPSend msg (with opcode) from CCIPSend data + struct (0x00000001) CCIPSend { queryId: uint64; destChainSelector: uint64; diff --git a/contracts/contracts/lib/utils.tolk b/contracts/contracts/lib/utils.tolk index bb497dec1..29ad53fba 100644 --- a/contracts/contracts/lib/utils.tolk +++ b/contracts/contracts/lib/utils.tolk @@ -172,12 +172,34 @@ fun Map.set(mutate self, key: address, value: V): void { return self.value.sDictSet(ADDR_KEY_LEN, key as slice, value.toCell().beginParse()); } +@inline @pure +fun Map.setIfNotExists(mutate self, key: address, value: V): bool { + return self.value.sDictSetIfNotExists(ADDR_KEY_LEN, key as slice, value.toCell().beginParse()); +} + +// TODO request TON Foundation to add this to tvm-dicts.tolk +@pure +fun dict.sDictSetIfNotExists(mutate self, keyLen: int, key: slice, value: slice): bool + asm(value key self keyLen) "DICTADD" + @inline @pure fun Map.has(self, key: address): bool { val (_, exists) = self.value.sDictGet(ADDR_KEY_LEN, key as slice); return exists; } +@inline @pure +fun Map.keys(self): tuple? { + var list: tuple? = null; + var (_address, value, found) = self.min(); + while (found) { + var address = _address! as address; + list = listPrepend(address, list); + (_address, value, found) = self.next(address); + }; + return list; +} + //Returns a lisp-list with the keys of the map, destroys the dictionary in the process @inline fun Map.keysDestructs(mutate self): tuple? { @@ -286,13 +308,19 @@ fun UMap.setRef(mutate self, key: K, value: V): void { @inline @pure fun UMap.min(self): (K?, V?, bool) { - val (key, value, exists) = self.value.uDictGetFirst(ADDR_KEY_LEN); + val (key, value, exists) = self.value.uDictGetFirst(self.keyLen); if (!exists) { return (null, null, false); } return (key, V.fromSlice(value!), true); } +@inline @pure +fun UMap.minLazy(self): (K?, slice?, bool) { + val (key, value, exists) = self.value.uDictGetFirst(self.keyLen); + return (key, value, exists); +} + @inline @pure fun UMap.next(self, key: K): (K?, V?, bool) { val (nextKey, value, exists) = self.value.uDictGetNext(self.keyLen, key); @@ -302,6 +330,12 @@ fun UMap.next(self, key: K): (K?, V?, bool) { return (nextKey, V.fromSlice(value!), true); } +@inline @pure +fun UMap.nextLazy(self, key: K): (K?, slice?, bool) { + val (nextKey, value, exists) = self.value.uDictGetNext(self.keyLen, key); + return (nextKey, value, exists); +} + @inline @pure fun UMap.replace(mutate self, key: K, value: V): bool { return (self.value.uDictSetIfExists(self.keyLen, key, value.toCell().beginParse())); @@ -309,7 +343,7 @@ fun UMap.replace(mutate self, key: K, value: V): bool { @inline @pure fun UMap.delete(mutate self, key: K): bool { - return self.value.sDictDelete(self.keyLen, key); + return self.value.sDictDelete(self.keyLen, key.toCell().beginParse()); } //Returns a lisp-list with the keys of the map, destroys the dictionary in the process @@ -326,6 +360,32 @@ fun UMap.keysDestructs(mutate self): tuple? { return list; } +//Returns a lisp-list with the keys of the map +@inline +fun UMap.keys(self): tuple? { + var list: tuple? = null; + var (_key, _value, found) = self.min(); + while (found) { + var key = _key! as K; + list = listPrepend(key, list); + (_key, _value, found) = self.next(key); + }; + return list; +} + +//Returns a lisp-list with the values of the map +@inline +fun UMap.values(self): tuple? { + var list: tuple? = null; + var (_key, _value, found) = self.min(); + while (found) { + var value = _value! as V; + var key = _key! as K; + list = listPrepend(value, list); + (_key, _value, found) = self.next(key); + }; + return list; +} struct TupleIterator { pos: int = 0; diff --git a/contracts/tests/Jetton.spec.ts b/contracts/tests/Jetton.spec.ts index 848352ec7..d254ef262 100644 --- a/contracts/tests/Jetton.spec.ts +++ b/contracts/tests/Jetton.spec.ts @@ -8,12 +8,11 @@ import { resolve } from 'path' import { readFileSync } from 'fs' import { execSync } from 'child_process' import { env } from 'process' +import { JettonMinterCode, JettonWalletCode } from '../wrappers/jetton/JettonCode' const ONCHAIN_CONTENT_PREFIX = 0x00 const OFFCHAIN_CONTENT_PREFIX = 0x01 -const PATH_CONTRACTS_JETTON = env.PATH_CONTRACTS_JETTON - const jettonDataURI = 'smartcontract.com' describe('Send and Receive Jettons', () => { @@ -463,29 +462,3 @@ function findGitRoot(): string { throw new Error('Could not find git repository root. Make sure you are in a git repository.') } } - -async function JettonMinterCode(): Promise { - const compiledPath = `${PATH_CONTRACTS_JETTON}/JettonMinter.compiled.json` - const compiled = JSON.parse(readFileSync(compiledPath, 'utf8')) - const hex = compiled.hex - if (!hex) { - throw new Error('Compiled JettonMinter code hex not found in JSON') - } - // Remove 0x prefix if present - const hexStr = hex.startsWith('0x') ? hex.slice(2) : hex - const boc = Buffer.from(hexStr, 'hex') - return Cell.fromBoc(boc)[0] -} - -async function JettonWalletCode(): Promise { - const compiledPath = `${PATH_CONTRACTS_JETTON}/JettonWallet.compiled.json` - const compiled = JSON.parse(readFileSync(compiledPath, 'utf8')) - const hex = compiled.hex - if (!hex) { - throw new Error('Compiled JettonWallet code hex not found in JSON') - } - // Remove 0x prefix if present - const hexStr = hex.startsWith('0x') ? hex.slice(2) : hex - const boc = Buffer.from(hexStr, 'hex') - return Cell.fromBoc(boc)[0] -} diff --git a/contracts/tests/ccip/CCIPRouter.spec.ts b/contracts/tests/ccip/CCIPRouter.spec.ts index a6e06611e..bfcf58996 100644 --- a/contracts/tests/ccip/CCIPRouter.spec.ts +++ b/contracts/tests/ccip/CCIPRouter.spec.ts @@ -2,7 +2,7 @@ import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox' import { toNano, Address, Cell, Dictionary, beginCell } from '@ton/core' import { compile } from '@ton/blueprint' import * as rt from '../../wrappers/ccip/Router' -import * as or from '../../wrappers/ccip/OnRamp' +import { OnRamp, OnRampStorage } from '../../wrappers/ccip/OnRamp' import { createTimestampedPriceValue, FeeQuoter, @@ -25,7 +25,7 @@ describe('Router', () => { let sender: SandboxContract let router: SandboxContract let feeQuoter: SandboxContract - let onRamp: SandboxContract + let onRamp: SandboxContract beforeAll(async () => { blockchain = await Blockchain.create() @@ -160,7 +160,7 @@ describe('Router', () => { // setup onramp { let code = await compile('OnRamp') - let data: or.OnRampStorage = { + let data: OnRampStorage = { ownable: { owner: deployer.address, pendingOwner: null, @@ -174,7 +174,7 @@ describe('Router', () => { destChainConfigs: Dictionary.empty(Dictionary.Keys.BigUint(64), Dictionary.Values.Cell()), } // TODO: use deployable to make deterministic? - onRamp = blockchain.openContract(or.OnRamp.createFromConfig(data, code)) + onRamp = blockchain.openContract(OnRamp.createFromConfig(data, code)) { const result = await onRamp.sendDeploy(deployer.getSender(), toNano('1')) expect(result.transactions).toHaveTransaction({ @@ -279,4 +279,215 @@ describe('Router', () => { }) } }) + + it('onramp token transfer - paid with TON', async () => { + // Configure onRamp on router + { + const result = await router.sendSetRamp(deployer.getSender(), { + value: toNano('1'), + queryID: 0, + destChainSelector: CHAINSEL_EVM_TEST_90000001, + onRamp: onRamp.address, + }) + expect(result.transactions).toHaveTransaction({ + from: deployer.address, + to: router.address, + success: true, + }) + } + + // Setup Jetton + const { jettonMinter, userWallet } = await setupJetton(blockchain, feeQuoter, deployer, sender) + + const senderJettonWallet = await userWallet(sender.address) + + const jettonAmount = toNano('1') + const ccipSend = rt.builder.message.in.ccipSend + .encode({ + queryID: 1, + destChainSelector: CHAINSEL_EVM_TEST_90000001, + receiver: Buffer.alloc(64), + data: Cell.EMPTY, + tokenAmounts: [{ amount: jettonAmount, token: jettonMinter.address }], + feeToken: ZERO_ADDRESS, + extraArgs: Cell.EMPTY, + }) + .asCell() + + const transferMsg: TransferMessage = { + queryId: 0n, + jettonAmount, + destination: router.address, + responseDestination: sender.address, + customPayload: null, + forwardTonAmount: toNano('1'), // TODO This should be derived from the fee + forwardPayload: ccipSend, + } + + // ccip send over jetton transfer + { + const result = await senderJettonWallet.sendTransfer(sender.getSender(), { + value: toNano('2'), + message: transferMsg, + }) + + const routerJettonWallet = await userWallet(router.address) + + // we called the router + expect(result.transactions).toHaveTransaction({ + from: routerJettonWallet.address, + to: router.address, + deploy: false, + success: true, + }) + // the router called the onRamp + expect(result.transactions).toHaveTransaction({ + from: router.address, + to: onRamp.address, + deploy: false, + success: true, + }) + // assert message went to feeQuoter + expect(result.transactions).toHaveTransaction({ + from: onRamp.address, + to: feeQuoter.address, + deploy: false, + success: true, + }) + + // destChainConfig -> feeQuoter -> onRamp + expect(result.transactions).toHaveTransaction({ + from: feeQuoter.address, + to: onRamp.address, + deploy: false, + success: true, + }) + + // assert CCIPMessageSent + assertLog(result.transactions, onRamp.address, LogTypes.CCIPMessageSent, { + message: { + header: { + destChainSelector: CHAINSEL_EVM_TEST_90000001, + }, + sender: sender.address, + }, + }) + } + }) }) + +async function setupJetton( + blockchain: Blockchain, + feeQuoter: SandboxContract, + deployer: SandboxContract, + user: SandboxContract, +) { + const jettonDataURI = 'smartcontract.com' + + const defaultContent = beginCell().storeStringTail(jettonDataURI).endCell() + + // get jetton wallet code + const jettonWalletCode = await JettonWalletCode() + + // deploy jetton minter + const jettonMinterCode = await JettonMinterCode() + const jettonMinter = blockchain.openContract( + JettonMinter.createFromConfig( + { + admin: deployer.address, + walletCode: jettonWalletCode, + jettonContent: defaultContent, + totalSupply: 0n, + }, + jettonMinterCode, + ), + ) + + const deployResult = await jettonMinter.sendDeploy(deployer.getSender(), toNano('1')) + + expect(deployResult.transactions).toHaveTransaction({ + from: deployer.address, + to: jettonMinter.address, + deploy: true, + }) + + // mint jettons to sender contract address as part of the setup + const mintResult = await jettonMinter.sendMint(deployer.getSender(), { + value: toNano('1'), + message: { + queryId: 0n, + destination: user.address, + tonAmount: toNano('0.05'), + jettonAmount: toNano('1'), + from: deployer.address, + responseDestination: deployer.address, + forwardTonAmount: 0n, + }, + }) + + expect(mintResult.transactions).toHaveTransaction({ + from: deployer.address, + to: jettonMinter.address, + success: true, + endStatus: 'active', + outMessagesCount: 1, // mint message + }) + + { + // TODO sendUpdatePrices to pay fees with LINK + // const result = await feeQuoter.sendUpdatePrices(deployer.getSender(), { + // value: toNano('1'), + // gasPrices: [], + // tokenPrices: [{ token: jettonMinter.address, price: 1n }], + // }) + // expect(result.transactions).toHaveTransaction({ + // to: feeQuoter.address, + // success: true, + // }) + } + + { + const result = await feeQuoter.sendUpdateTokenTransferFeeConfigs(deployer.getSender(), { + value: toNano('1'), + msg: { + updates: new Map([ + [ + CHAINSEL_EVM_TEST_90000001, + { + add: new Map([ + [ + jettonMinter.address, + { + isEnabled: true, + minFeeUsdCents: 1, + maxFeeUsdCents: 100, + deciBps: 0, + destGasOverhead: 0, + destBytesOverhead: 0, + }, + ], + ]), + remove: [], + }, + ], + ]), + }, + }) + expect(result.transactions).toHaveTransaction({ + from: deployer.address, + to: feeQuoter.address, + success: true, + }) + } + + const userWallet = async (address: Address) => { + return blockchain.openContract( + JettonWallet.createFromAddress(await jettonMinter.getWalletAddress(address)), + ) + } + + return { + jettonMinter, + userWallet, + } +} diff --git a/contracts/wrappers/examples/jetton/JettonSender.ts b/contracts/wrappers/examples/jetton/JettonSender.ts index 5bfb07173..5da03544e 100644 --- a/contracts/wrappers/examples/jetton/JettonSender.ts +++ b/contracts/wrappers/examples/jetton/JettonSender.ts @@ -8,7 +8,7 @@ import { Sender, SendMode, } from '@ton/core' -import { JettonClientConfig, jettonClientConfigToCell, JettonOpcodes } from './types' +import { JettonClientConfig, builder, JettonOpcodes } from './types' import { crc32 } from 'zlib' export type JettonSenderConfig = { @@ -16,7 +16,7 @@ export type JettonSenderConfig = { } export function jettonSenderConfigToCell(config: JettonSenderConfig): Cell { - return jettonClientConfigToCell(config.jettonClient) + return builder.data.traitData.encode(config.jettonClient).asCell() } export const SenderOpcodes = { diff --git a/contracts/wrappers/examples/jetton/OnrampMock.ts b/contracts/wrappers/examples/jetton/OnrampMock.ts index 8e80f7e39..962ec25df 100644 --- a/contracts/wrappers/examples/jetton/OnrampMock.ts +++ b/contracts/wrappers/examples/jetton/OnrampMock.ts @@ -8,14 +8,14 @@ import { Sender, SendMode, } from '@ton/core' -import { JettonClientConfig, jettonClientConfigToCell, ErrorCodes } from './types' +import { JettonClientConfig, builder, ErrorCodes } from './types' export type OnrampMockConfig = { jettonClient: JettonClientConfig } export function onrampMockConfigToCell(config: OnrampMockConfig): Cell { - return jettonClientConfigToCell(config.jettonClient) + return builder.data.traitData.encode(config.jettonClient).asCell() } export const OnrampConstants = { diff --git a/contracts/wrappers/examples/jetton/SimpleJettonReceiver.ts b/contracts/wrappers/examples/jetton/SimpleJettonReceiver.ts index e21ebaf6e..a3534610b 100644 --- a/contracts/wrappers/examples/jetton/SimpleJettonReceiver.ts +++ b/contracts/wrappers/examples/jetton/SimpleJettonReceiver.ts @@ -8,7 +8,7 @@ import { Sender, SendMode, } from '@ton/core' -import { JettonClientConfig, jettonClientConfigToCell } from './types' +import { JettonClientConfig, builder } from './types' export type SimpleJettonReceiverConfig = { jettonClient: JettonClientConfig @@ -17,22 +17,22 @@ export type SimpleJettonReceiverConfig = { } export function simpleJettonReceiverConfigToCell(config: SimpleJettonReceiverConfig): Cell { - const builder = beginCell() + const s = beginCell() // Store JettonClient - builder.storeRef(jettonClientConfigToCell(config.jettonClient)) + s.storeRef(builder.data.traitData.encode(config.jettonClient).asCell()) // Store amountChecker - builder.storeCoins(config.amountChecker) + s.storeCoins(config.amountChecker) // Store payloadChecker (optional cell) if (config.payloadChecker) { - builder.storeBit(1).storeRef(config.payloadChecker) + s.storeBit(1).storeRef(config.payloadChecker) } else { - builder.storeBit(0) + s.storeBit(0) } - return builder.endCell() + return s.endCell() } export class SimpleJettonReceiver implements Contract { diff --git a/contracts/wrappers/examples/jetton/types.ts b/contracts/wrappers/examples/jetton/types.ts index ce3b82685..801048861 100644 --- a/contracts/wrappers/examples/jetton/types.ts +++ b/contracts/wrappers/examples/jetton/types.ts @@ -1,12 +1,29 @@ -import { Address, Cell, beginCell } from '@ton/core' +import { Address, Builder, Cell, Slice, beginCell } from '@ton/core' +import { CellCodec } from '../../utils' export type JettonClientConfig = { masterAddress: Address jettonWalletCode: Cell } -export function jettonClientConfigToCell(config: JettonClientConfig): Cell { - return beginCell().storeAddress(config.masterAddress).storeRef(config.jettonWalletCode).endCell() +export const builder = { + data: (() => { + const traitData: CellCodec = { + encode: (config: JettonClientConfig): Builder => { + return beginCell().storeAddress(config.masterAddress).storeRef(config.jettonWalletCode) + }, + load: (src: Slice): JettonClientConfig => { + return { + masterAddress: src.loadAddress(), + jettonWalletCode: src.loadRef(), + } + }, + } + + return { + traitData, + } + })(), } export const JettonOpcodes = { diff --git a/contracts/wrappers/jetton/JettonMinter.ts b/contracts/wrappers/jetton/JettonMinter.ts index ca333628b..4dee95361 100644 --- a/contracts/wrappers/jetton/JettonMinter.ts +++ b/contracts/wrappers/jetton/JettonMinter.ts @@ -111,7 +111,7 @@ export class JettonMinter implements Contract { await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell().endCell(), + body: Cell.EMPTY, }) } diff --git a/contracts/wrappers/jetton/JettonWallet.ts b/contracts/wrappers/jetton/JettonWallet.ts index 469053cbb..83613482c 100644 --- a/contracts/wrappers/jetton/JettonWallet.ts +++ b/contracts/wrappers/jetton/JettonWallet.ts @@ -85,7 +85,7 @@ export class JettonWallet implements Contract { await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell().endCell(), + body: Cell.EMPTY, }) } From 719e287f05050d5c8d73e6e838373f27ea80d199 Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Thu, 11 Sep 2025 19:02:54 -0300 Subject: [PATCH 8/8] feat: token pool translation --- contracts/contracts/lib/pools/ipool.tolk | 51 ++ contracts/contracts/lib/pools/pool.tolk | 63 ++ .../contracts/lib/pools/rate_limiter.tolk | 159 ++++ contracts/contracts/lib/pools/token_pool.tolk | 860 ++++++++++++++++++ .../lib/pools/token_pool/errors.tolk | 19 + .../pools/token_pool/external_messages.tolk | 91 ++ .../lib/pools/token_pool/messages.tolk | 350 +++++++ .../lib/pools/token_pool/storage.tolk | 58 ++ .../pools/lock_release_token_pool.tolk | 281 ++++++ .../pools/lock_release_token_pool/errors.tolk | 4 + .../external_messages.tolk | 33 + .../lock_release_token_pool/messages.tolk | 87 ++ .../lock_release_token_pool/storage.tolk | 29 + contracts/tests/pools/BaseTest.ts | 316 +++++++ ...LockReleaseTokenPool.setRebalancer.spec.ts | 158 ++++ .../tests/pools/LockReleaseTokenPoolSetup.ts | 245 +++++ .../pools.LockReleaseTokenPool.compile.ts | 8 + .../wrappers/pools/LockReleaseTokenPool.ts | 606 ++++++++++++ contracts/wrappers/pools/RateLimiter.ts | 82 ++ contracts/wrappers/pools/TokenPools.ts | 190 ++++ contracts/wrappers/pools/index.ts | 3 + 21 files changed, 3693 insertions(+) create mode 100644 contracts/contracts/lib/pools/ipool.tolk create mode 100644 contracts/contracts/lib/pools/pool.tolk create mode 100644 contracts/contracts/lib/pools/rate_limiter.tolk create mode 100644 contracts/contracts/lib/pools/token_pool.tolk create mode 100644 contracts/contracts/lib/pools/token_pool/errors.tolk create mode 100644 contracts/contracts/lib/pools/token_pool/external_messages.tolk create mode 100644 contracts/contracts/lib/pools/token_pool/messages.tolk create mode 100644 contracts/contracts/lib/pools/token_pool/storage.tolk create mode 100644 contracts/contracts/pools/lock_release_token_pool.tolk create mode 100644 contracts/contracts/pools/lock_release_token_pool/errors.tolk create mode 100644 contracts/contracts/pools/lock_release_token_pool/external_messages.tolk create mode 100644 contracts/contracts/pools/lock_release_token_pool/messages.tolk create mode 100644 contracts/contracts/pools/lock_release_token_pool/storage.tolk create mode 100644 contracts/tests/pools/BaseTest.ts create mode 100644 contracts/tests/pools/LockReleaseTokenPool.setRebalancer.spec.ts create mode 100644 contracts/tests/pools/LockReleaseTokenPoolSetup.ts create mode 100644 contracts/wrappers/pools.LockReleaseTokenPool.compile.ts create mode 100644 contracts/wrappers/pools/LockReleaseTokenPool.ts create mode 100644 contracts/wrappers/pools/RateLimiter.ts create mode 100644 contracts/wrappers/pools/TokenPools.ts create mode 100644 contracts/wrappers/pools/index.ts diff --git a/contracts/contracts/lib/pools/ipool.tolk b/contracts/contracts/lib/pools/ipool.tolk new file mode 100644 index 000000000..b04275b6e --- /dev/null +++ b/contracts/contracts/lib/pools/ipool.tolk @@ -0,0 +1,51 @@ +import "./pool" +// import "./ipool/messages" + +tolk 1.0 + +struct IPool { + context: T + hooks: IPool_Hooks +} + +struct IPool_Hooks { + lockOrBurn: ((T, address, coins, Pool_LockOrBurnInV1) -> Pool_LockOrBurnOutV1) + releaseOrMint: ((T, address, coins, Pool_ReleaseOrMintInV1) -> Pool_ReleaseOrMintOutV1) +} + +type IPool_InMessage = + | Pool_LockOrBurnInV1 + | Pool_ReleaseOrMintInV1 + +type IPool_Response = + | Pool_LockOrBurnOutV1 + | Pool_ReleaseOrMintOutV1 + +/// @notice Handles incoming messages for IPool +@inline +fun IPool.onInternalMessage( + mutate self, + sender: address, + msgValue: coins, + msgBody: slice, +): bool { + val msg = lazy IPool_InMessage.fromSlice(msgBody); + var response: IPool_Response? = null; + + match (msg) { + Pool_LockOrBurnInV1 => + response = self.hooks.lockOrBurn(self.context, sender, msgValue, msg), + Pool_ReleaseOrMintInV1 => + response = self.hooks.releaseOrMint(self.context, sender, msgValue, msg), + else => return false + } + + createMessage({ + bounce: false, + value: 0, + dest: sender, + body: response, + }).send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); + return true +} + diff --git a/contracts/contracts/lib/pools/pool.tolk b/contracts/contracts/lib/pools/pool.tolk new file mode 100644 index 000000000..ba196072b --- /dev/null +++ b/contracts/contracts/lib/pools/pool.tolk @@ -0,0 +1,63 @@ +import "../../ccip/types" + +tolk 1.0 + +// The tag used to signal support for the pool v1 standard. +// bytes4(keccak256("CCIP_POOL_V1")) +// const CCIP_POOL_V1 : bytes4 = 0xaff2afbf as bytes4 // TODO we don't know if we need this + +// The number of bytes in the return data for a pool v1 releaseOrMint call. +// This should match the size of the ReleaseOrMintOutV1 struct. +const CCIP_POOL_V1_RET_BYTES : uint16 = 32 + +// The default max number of bytes in the return data for a pool v1 lockOrBurn call. +// This data can be used to send information to the destination chain token pool. Can be overwritten +// in the TokenTransferFeeConfig.destBytesOverhead if more data is required. +const CCIP_LOCK_OR_BURN_V1_RET_BYTES : uint32 = 32 + +//crc32('Pool_LockOrBurnInV1') +struct (0x179B4A8C)Pool_LockOrBurnInV1 { + msg: Cell + receiver: CrossChainAddress // The recipient of the tokens on the destination chain, abi encoded. + remoteChainSelector: uint64 // ─╮ The chain ID of the destination chain. + originalSender: address // ─────╯ The original sender of the tx on the source chain. + amount: coins // The amount of tokens to lock or burn, denominated in the source token's decimals. + localToken: address // The address on this chain of the token to lock or burn. +} + +//crc32('Pool_LockOrBurnOutV1') +struct (0x56E7EB1A)Pool_LockOrBurnOutV1 { + msg: Cell + // The address of the destination token, abi encoded in the case of EVM chains. + // This value is UNTRUSTED as any pool owner can return whatever value they want. + destTokenAddress: CrossChainAddress + // Optional pool data to be transferred to the destination chain. Be default this is capped at + // CCIP_LOCK_OR_BURN_V1_RET_BYTES bytes. If more data is required, the TokenTransferFeeConfig.destBytesOverhead + // has to be set for the specific token. + destPoolData: slice +} + +//crc32('Pool_ReleaseOrMintInV1') +struct (0x1703AC0B)Pool_ReleaseOrMintInV1 { + msg: Cell + originalSender: CrossChainAddress // The original sender of the tx on the source chain. + remoteChainSelector: uint64 // ───╮ The chain ID of the source chain. + receiver: address // ─────────────╯ The recipient of the tokens on the destination chain. + sourceDenominatedAmount: coins // The amount of tokens to release or mint, denominated in the source token's decimals. + localToken: address // The address on this chain of the token to release or mint. + /// @dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the + /// expected pool address for the given remoteChainSelector. + sourcePoolAddress: CrossChainAddress // The address of the source pool, abi encoded in the case of EVM chains. + sourcePoolData: CrossChainAddress // The data received from the source pool to process the release or mint. + /// @dev WARNING: offchainTokenData is untrusted data. + offchainTokenData: CrossChainAddress // The offchain data to process the release or mint. +} + +//crc32('Pool_ReleaseOrMintOutV1') +struct (0x0D347F43)Pool_ReleaseOrMintOutV1 { + msg: Cell + // The number of tokens released or minted on the destination chain, denominated in the local token's decimals. + // This value is expected to be equal to the ReleaseOrMintInV1.amount in the case where the source and destination + // chain have the same number of decimals. + destinationAmount: coins +} diff --git a/contracts/contracts/lib/pools/rate_limiter.tolk b/contracts/contracts/lib/pools/rate_limiter.tolk new file mode 100644 index 000000000..759b83b9f --- /dev/null +++ b/contracts/contracts/lib/pools/rate_limiter.tolk @@ -0,0 +1,159 @@ +import "../utils" + +tolk 1.0 + +const RateLimiter_ERROR_BUCKET_OVERFILLED = 1001 +const RateLimiter_ERROR_TOKEN_MAX_CAPACITY_EXCEEDED = 1002 +const RateLimiter_ERROR_TOKEN_RATE_LIMIT_REACHED = 1003 +const RateLimiter_ERROR_INVALID_RATE_LIMIT_RATE = 1004 +const RateLimiter_ERROR_DISABLED_NON_ZERO_RATE_LIMIT = 1005 + +//crc32("RateLimiter_ConfigChanged") +const RATELIMITER_CONFIGCHANGED_TOPIC: int = stringCrc32("RateLimiter_ConfigChanged"); + +/// @notice Event // TODO complete description +struct RateLimiter_ConfigChanged { config: RateLimiter_Config } + +struct RateLimiter_TokenBucket { + tokens: uint128 // ────╮ Current number of tokens that are in the bucket. + lastUpdated: uint32 // │ Timestamp in seconds of the last token refill, good for 100+ years. + isEnabled: bool // ────╯ Indication whether the rate limiting is enabled or not. + capacity: uint128 // ──╮ Maximum number of tokens that can be in the bucket. + rate: uint128 // ──────╯ Number of tokens per second that the bucket is refilled. +} + +const RATE_LIMITER_CONFIG_LEN = 1 + 128 + 128 + +struct RateLimiter_Config { + isEnabled: bool // Indication whether the rate limiting should be enabled. + capacity: uint128 // ──╮ Specifies the capacity of the rate limiter. + rate: uint128 // ──────╯ Specifies the rate of the rate limiter. +} + +struct RateLimiter_Data { + bucket: RateLimiter_TokenBucket // The token bucket that is used for rate limiting. +} + +struct RateLimiter { + data: RateLimiter_Data +} + +/// @notice _consume removes the given tokens from the pool, lowering the rate tokens allowed to be +/// consumed for subsequent calls. +/// @param requestTokens The total tokens to be consumed from the bucket. +/// @param tokenAddress The token to consume capacity for, use 0x0 to indicate aggregate value capacity. +/// @dev Reverts when requestTokens exceeds bucket capacity or available tokens in the bucket. +/// @dev emits removal of requestTokens if requestTokens is > 0. +fun RateLimiter_TokenBucket._consume(mutate self, requestTokens: coins, tokenAddress: address) { + // If there is no value to remove or rate limiting is turned off, skip this step to reduce gas usage. + if (!self.isEnabled || requestTokens == 0) { + return; + } + + var tokens = self.tokens; + var capacity = self.capacity; + var timeDiff = blockchain.now() - self.lastUpdated; + + if (timeDiff != 0) { + if (tokens > capacity) { + throw RateLimiter_ERROR_BUCKET_OVERFILLED; // TODO this should be a return? revert BucketOverfilled(); + } + + // Refill tokens when arriving at a new block time. + tokens = _calculateRefill(capacity, tokens, timeDiff, self.rate); + + self.lastUpdated = blockchain.now(); + } + + if (capacity < requestTokens) { + throw RateLimiter_ERROR_TOKEN_MAX_CAPACITY_EXCEEDED; // TODO this should be a return? revert TokenMaxCapacityExceeded(capacity, requestTokens, tokenAddress); + } + if (tokens < requestTokens) { + var rate = self.rate; + // Wait required until the bucket is refilled enough to accept this value, round up to next higher second. + // Consume is not guaranteed to succeed after wait time passes if there is competing traffic. + // This acts as a lower bound of wait time. + var minWaitInSeconds = ((requestTokens - tokens) + (rate - 1)) / rate; + + throw RateLimiter_ERROR_TOKEN_RATE_LIMIT_REACHED; // TODO this should be a return? revert TokenRateLimitReached(minWaitInSeconds, tokens, tokenAddress); + } + tokens -= requestTokens; + + // Downcast is safe here, as tokens is not larger than capacity. + self.tokens = tokens; +} + +/// @notice Gets the token bucket with its values for the block it was requested at. +/// @return The token bucket. +fun RateLimiter_TokenBucket._currentTokenBucketState(mutate self): RateLimiter_TokenBucket { + // We update the bucket to reflect the status at the exact time of the call. This means we might need to refill a + // part of the bucket based on the time that has passed since the last update. + self.tokens = _calculateRefill( + self.capacity, + self.tokens, + blockchain.now() - self.lastUpdated, + self.rate + ); + self.lastUpdated = blockchain.now(); + return self; +} + +/// @notice Sets the rate limited config. +/// @param s_bucket The token bucket. +/// @param config The new config. +fun RateLimiter._setTokenBucketConfig(mutate self, config: RateLimiter_Config) { + // First update the bucket to make sure the proper rate is used for all the time up until the config change. + var timeDiff: uint256 = blockchain.now() - self.data.bucket.lastUpdated; + if (timeDiff != 0) { + self.data.bucket.tokens = _calculateRefill( + self.data.bucket.capacity, + self.data.bucket.tokens, + timeDiff, + self.data.bucket.rate + ); + + self.data.bucket.lastUpdated = blockchain.now(); + } + + self.data.bucket.tokens = _min(config.capacity, self.data.bucket.tokens); + // TBD why doesn't RateLimiter have a `config: Config` attribute instead of repeating the fields? + self.data.bucket.isEnabled = config.isEnabled; + self.data.bucket.capacity = config.capacity; + self.data.bucket.rate = config.rate; + + emit(RATELIMITER_CONFIGCHANGED_TOPIC, RateLimiter_ConfigChanged{ config }); +} + +/// @notice Validates the token bucket config. +fun RateLimiter._validateTokenBucketConfig(config: RateLimiter_Config) { + if (config.isEnabled) { + if (config.rate > config.capacity) { + throw RateLimiter_ERROR_INVALID_RATE_LIMIT_RATE; // TODO this should be a return? revert InvalidRateLimitRate(config); + } + } else { + if (config.rate != 0 || config.capacity != 0) { + throw RateLimiter_ERROR_DISABLED_NON_ZERO_RATE_LIMIT; // TODO this should be a return? revert DisabledNonZeroRateLimit(config); + } + } +} + +/// @notice Calculate refilled tokens. +/// @param capacity bucket capacity. +/// @param tokens current bucket tokens. +/// @param timeDiff block time difference since last refill. +/// @param rate bucket refill rate. +/// @return the value of tokens after refill. +fun _calculateRefill(capacity: uint128, tokens: uint128, timeDiff: uint256, rate: uint128): uint128 { + return _min(capacity, (tokens as uint256 + timeDiff * rate as uint256) as uint128); +} + +/// @notice Return the smallest of two integers. +/// @param a first int. +/// @param b second int. +/// @return smallest. +fun _min(a: uint128, b: uint128): uint128 { + if (a < b) { + return a; + } + return b; +} diff --git a/contracts/contracts/lib/pools/token_pool.tolk b/contracts/contracts/lib/pools/token_pool.tolk new file mode 100644 index 000000000..64891d5df --- /dev/null +++ b/contracts/contracts/lib/pools/token_pool.tolk @@ -0,0 +1,860 @@ +// import {IERC20} from +// "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.tolk" +// import {IERC20Metadata} from +// "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.tolk" +// import {IERC165} from +// "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.tolk" +// import {EnumerableSet} from +// "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v5.0.2/contracts/utils/structs/EnumerableSet.sol"; +import "@stdlib/tvm-dicts" +import "../../ccip/types" +import "../access/ownable_2step" +// import {IPoolV1} from "../interfaces/IPool" +// import {IRMN} from "../interfaces/IRMN" +// import {IRouter} from "../interfaces/IRouter" +import "pool" +import "rate_limiter" +import "../jetton/jetton_client" +import "../utils" + +import "./ipool" + +import "./token_pool/storage" +import "./token_pool/messages" +import "./token_pool/errors" +import "./token_pool/external_messages" + +tolk 1.0 + +/// @notice Base abstract class with common functions for all token pools. +/// A token pool serves as isolated place for holding tokens and token specific logic +/// that may execute as tokens move across the bridge. +/// @dev This pool supports different decimals on different chains but using this feature could impact the total number +/// of tokens in circulation. Since all of the tokens are locked/burned on the source, and a rounded amount is +/// minted/released on the destination, the number of tokens minted/released could be less than the number of tokens +/// burned/locked. This is because the source chain does not know about the destination token decimals. This is not a +/// problem if the decimals are the same on both chains. +/// +/// Example: +/// Assume there is a token with 6 decimals on chain A and 3 decimals on chain B. +/// - 1.234567 tokens are burned on chain A. +/// - 1.234 tokens are minted on chain B. +/// When sending the 1.234 tokens back to chain A, you will receive 1.234000 tokens on chain A, effectively losing +/// 0.000567 tokens. +/// In the case of a burnMint pool on chain A, these funds are burned in the pool on chain A. +/// In the case of a lockRelease pool on chain A, these funds accumulate in the pool on chain A. + +/// @dev TokenPool contract struct with optional hooks for virtual functions +struct TokenPool { + data: TokenPool_Data + /// Dependencies + + /// Ownable trait data + ownable: Ownable2Step, + + /// Runtime hooks (extensions) + context: T? = null + hooks: TokenPool_Hooks +} + +// --- Hooks struct (extensions) --- +/// @dev Hook extensions exposed by the TokenPool contract. +struct TokenPool_Hooks { + /// Hook for getRouter virtual function override + getRouter: ((T?) -> address)? + /// Hook for getTokenDecimals virtual function override + getTokenDecimals: ((T?) -> uint8)? + /// Hook for _encodeLocalDecimals virtual function override + encodeLocalDecimals: ((T?) -> slice)? + /// Hook for _calculateLocalAmount virtual function override + calculateLocalAmount: ((T?, coins, uint8) -> coins)? + + _parseRemoteDecimals: ((T?, slice) -> uint8)? +} + +// --- Methods --- +/// @dev Loads the TokenPool data from cell. +fun TokenPool.load(dataCell: cell, ownable: Ownable2Step, ipool_hooks: IPool_Hooks, context: T? = null, hooks: TokenPool_Hooks? = null): TokenPool { + // Load the contract data from the persistent storage + var data = TokenPool_Data.fromCell(dataCell); + + // Return the TokenPool instance with loaded data and hooks + return TokenPool { + data, + ownable, + context, + hooks, + }; +} + +fun TokenPool.ipool(self): IPool { + return IPool { + context: self.context!, + hooks: self.hooks.ipool + }; +} + +// --- Message handlers --- +/// @notice Handles incoming messages for TokenPool +@inline +fun TokenPool.onInternalMessage( + mutate self, + sender: address, + msgValue: coins, + msgBody: slice, +): bool { + val msg = lazy TokenPool_InMessage.fromSlice(msgBody); + var response: TokenPool_Response? = null; + + match (msg) { + TokenPool_IsSupportedToken => { + response = TokenPool_IsSupportedTokenResponse { + queryId: msg.queryId, + isSupported: self.isSupportedToken(msg.token), + }; + } + TokenPool_GetToken => { + response = TokenPool_GetTokenResponse { + queryId: msg.queryId, + token: self.getToken(), + }; + } + TokenPool_GetRouter => { + response = TokenPool_GetRouterResponse { + queryId: msg.queryId, + router: self.getRouter(), + }; + } + TokenPool_SetRouter => { + self.setRouter(sender, msg.newRouter); + response = TokenPool_SetRouterResponse { queryId: msg.queryId }; + } + // TODO dont know if we even need this + // TokenPool_SupportsInterface => { + // response = TokenPool_SupportsInterfaceResponse { + // queryId: msg.queryId, + // supported: self.supportsInterface(msg.interfaceId), + // }; + // } + // Add all remaining message handlers + TokenPool_GetRmnProxy => { + response = TokenPool_GetRmnProxyResponse { + queryId: msg.queryId, + rmnProxy: self.getRmnProxy(), + }; + } + TokenPool_GetTokenDecimals => { + response = TokenPool_GetTokenDecimalsResponse { + queryId: msg.queryId, + decimals: self.getTokenDecimals(), + }; + } + TokenPool_GetRemotePools => { + response = TokenPool_GetRemotePoolsResponse { + queryId: msg.queryId, + remotePools: self.getRemotePools(msg.remoteChainSelector), + }; + } + TokenPool_IsRemotePool => { + response = TokenPool_IsRemotePoolResponse { + queryId: msg.queryId, + isRemotePool: self.isRemotePool(msg.remoteChainSelector, msg.remotePoolAddress), + }; + } + TokenPool_GetRemoteToken => { + response = TokenPool_GetRemoteTokenResponse { + queryId: msg.queryId, + remoteToken: self.getRemoteToken(msg.remoteChainSelector), + }; + } + TokenPool_AddRemotePool => { + self.addRemotePool( + sender, + msg.remoteChainSelector, + msg.remotePoolAddress + ); + response = TokenPool_AddRemotePoolResponse { queryId: msg.queryId, success: true }; // TODO do we expect to revert or should we try-catch and emit success: false? + } + TokenPool_RemoveRemotePool => { + self.removeRemotePool( + sender, + msg.remoteChainSelector, + msg.remotePoolAddress + ); + response = TokenPool_RemoveRemotePoolResponse { queryId: msg.queryId, success: true }; + } + TokenPool_IsSupportedChain => { + response = TokenPool_IsSupportedChainResponse { + queryId: msg.queryId, + isSupported: self.isSupportedChain(msg.remoteChainSelector), + }; + } + TokenPool_GetSupportedChains => { + response = TokenPool_GetSupportedChainsResponse { + queryId: msg.queryId, + supportedChains: self.getSupportedChains(), + }; + } + TokenPool_ApplyChainUpdates => { + self.applyChainUpdates(sender, msg.chainUpdates); + response = TokenPool_ApplyChainUpdatesResponse { queryId: msg.queryId, success: true }; + } + TokenPool_SetRateLimitAdmin => { + self.setRateLimitAdmin(sender, msg.rateLimitAdmin); + response = TokenPool_SetRateLimitAdminResponse { queryId: msg.queryId, success: true }; + } + TokenPool_GetRateLimitAdmin => { + response = TokenPool_GetRateLimitAdminResponse { + queryId: msg.queryId, + rateLimitAdmin: self.getRateLimitAdmin(), + }; + } + TokenPool_GetCurrentOutboundRateLimiterState => { + response = TokenPool_GetCurrentOutboundRateLimiterStateResponse { + queryId: msg.queryId, + state: self.getCurrentOutboundRateLimiterState(msg.remoteChainSelector), + }; + } + TokenPool_GetCurrentInboundRateLimiterState => { + response = TokenPool_GetCurrentInboundRateLimiterStateResponse { + queryId: msg.queryId, + state: self.getCurrentInboundRateLimiterState(msg.remoteChainSelector), + }; + } + TokenPool_GetAllowListEnabled => { + response = TokenPool_GetAllowListEnabledResponse { + queryId: msg.queryId, + enabled: self.getAllowListEnabled(), + }; + } + TokenPool_GetAllowList => { + response = TokenPool_GetAllowListResponse { + queryId: msg.queryId, + allowList: self.getAllowList(), + }; + } + TokenPool_ApplyAllowListUpdates => { + self.applyAllowListUpdates(sender, msg.removes, msg.adds); + response = TokenPool_ApplyAllowListUpdatesResponse { + queryId: msg.queryId, + success: true, + }; + } + else => { + } + } + + if (response == null) { + return false; + } + createMessage({ + bounce: false, + value: 0, + dest: sender, + body: response, + }).send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); + return true; +} + + +/// @notice Gets the pool's Router +/// @return router The pool's Router +/// @notice Hook-aware implementation of getRouter +fun TokenPool.getRouter(self): address { + if (self.hooks.getRouter != null) { + return self.hooks.getRouter(self.context); // extension hook + } + + return self.data.s_router; +} + +// --- Internal functions --- +/// @inheritdoc IPoolV1 +fun TokenPool.isSupportedToken(self, token: address): bool { + return token == self.data.i_token.load().masterAddress; +} + +/// @notice Gets the IERC20 token that this pool can lock or burn. +/// @return token The IERC20 token representation. +fun TokenPool.getToken(self): address { + return self.data.i_token.load().masterAddress; +} + +/// @notice Get RMN proxy address +/// @return rmnProxy address of RMN proxy +fun TokenPool.getRmnProxy(self): address { + throw 1000 + // return self.data.i_rmnProxy; +} + +/// @notice Sets the pool's Router +/// @param newRouter The new Router +fun TokenPool.setRouter(mutate self, sender: address, newRouter: address) { + self.ownable.requireOwner(sender); + + if (newRouter == createAddressNone()) { + throw (TokenPool_ERROR_ZERO_ADDRESS_INVALID); + } + var oldRouter: address = self.data.s_router; + self.data.s_router = newRouter; + + emit(TOKENPOOL_ROUTERUPDATED_TOPIC, TokenPool_RouterUpdated { oldRouter, newRouter }); +} + +/// @notice Signals which version of the pool interface is supported +// TODO dont know if we even need this +// fun TokenPool.supportsInterface(self, interfaceId: bytes4): bool { +// return interfaceId == Pool_CCIP_POOL_V1 || interfaceId == type(IPoolV1).interfaceId || +// interfaceId == type(IERC165).interfaceId; +// } + + + +// ================================================================ +// │ Validation │ +// ================================================================ +/// @notice Validates the lock or burn input for correctness on +/// - token to be locked or burned +/// - RMN curse status +/// - allowlist status +/// - if the sender is a valid onRamp +/// - rate limit status +/// @param lockOrBurnIn The input to validate. +/// @dev This function should always be called before executing a lock or burn. Not doing so would allow +/// for various exploits. +fun TokenPool._validateLockOrBurn(mutate self, sender: address, lockOrBurnIn: Pool_LockOrBurnInV1) { + if (!self.isSupportedToken(lockOrBurnIn.localToken)) { + throw (TokenPool_ERROR_INVALID_TOKEN); + } + // if (IRMN(self.i_rmnProxy).isCursed(bytes16(uint128(lockOrBurnIn.remoteChainSelector)))) { + // // revert CursedByRMN() // TBD should return instead? + // throw TokenPool_ERROR_CURSED_BY_RMN + // } + self._checkAllowList(lockOrBurnIn.originalSender); + + self._onlyOnRamp(sender, lockOrBurnIn.remoteChainSelector); + self._consumeOutboundRateLimit(lockOrBurnIn.remoteChainSelector, lockOrBurnIn.amount); +} + +/// @notice Validates the release or mint input for correctness on +/// - token to be released or minted +/// - RMN curse status +/// - if the sender is a valid offRamp +/// - if the source pool is valid +/// - rate limit status +/// @param releaseOrMintIn The input to validate. +/// @param localAmount The local amount to be released or minted. +/// @dev This function should always be called before executing a release or mint. Not doing so would allow +/// for various exploits. +fun TokenPool._validateReleaseOrMint( + mutate self, + releaseOrMintIn: Pool_ReleaseOrMintInV1, + localAmount: coins, +) { + if (!self.isSupportedToken(releaseOrMintIn.localToken)) { + throw (TokenPool_ERROR_INVALID_TOKEN); + } + // if (IRMN(self.i_rmnProxy).isCursed(bytes16(uint128(releaseOrMintIn.remoteChainSelector)))) { + // // revert CursedByRMN() // TBD should return instead? + // throw TokenPool_ERROR_CURSED_BY_RMN + // } + // self._onlyOffRamp(releaseOrMintIn.remoteChainSelector); + + // Validates that the source pool address is configured on this pool. + if (!self.isRemotePool(releaseOrMintIn.remoteChainSelector, releaseOrMintIn.sourcePoolAddress)) { + { + throw ( + TokenPool_ERROR_INVALID_SOURCE_POOL_ADDRESS + ); + } + } + + self._consumeInboundRateLimit(releaseOrMintIn.remoteChainSelector, localAmount); +} + +// ================================================================ +// │ Token decimals │ +// ================================================================ +/// @notice Gets the IERC20 token decimals on the local chain. +/// @notice Hook-aware implementation of getTokenDecimals +fun TokenPool.getTokenDecimals(self): uint8 { + if (self.hooks.getTokenDecimals != null) { + return self.hooks.getTokenDecimals(self.context); // extension hook + } + return self.data.i_tokenDecimals; +} + +/// @notice Hook-aware implementation of _encodeLocalDecimals +fun TokenPool._encodeLocalDecimals(self): slice { + if (self.hooks.encodeLocalDecimals != null) { + return self.hooks.encodeLocalDecimals(self.context); // extension hook + } + + return beginCell().storeUint(self.data.i_tokenDecimals, 8).endCell().beginParse(); +} + +fun TokenPool._parseRemoteDecimals(self, sourcePoolData: slice): uint8 { + if (self.hooks._parseRemoteDecimals != null) { + return self.hooks._parseRemoteDecimals(null, sourcePoolData); + } + // Fallback to the local token decimals if the source pool data is empty. This allows for backwards compatibility. + if (sourcePoolData.isEmpty()) { + return self.data.i_tokenDecimals; + } + if (sourcePoolData.remainingBitsCount() != 32*8) { + throw (TokenPool_ERROR_INVALID_REMOTE_CHAIN_DECIMALS); + } + val remoteDecimals = sourcePoolData.loadUint(256); + if (remoteDecimals > 0xff ) { // type(uint8).max + throw (TokenPool_ERROR_INVALID_REMOTE_CHAIN_DECIMALS); + } + return remoteDecimals as uint8; +} + +/// @notice Calculates the local amount based on the remote amount and decimals. +/// @param remoteAmount The amount on the remote chain. +/// @param remoteDecimals The decimals of the token on the remote chain. +/// @return The local amount. +/// @dev This function protects against overflows. If there is a transaction that hits the overflow check, it is +/// probably incorrect as that means the amount cannot be represented on this chain. If the local decimals have been +/// wrongly configured, the token issuer could redeploy the pool with the correct decimals and manually re-execute the +/// CCIP tx to fix the issue. +/// @notice Hook-aware implementation of _calculateLocalAmount +fun TokenPool._calculateLocalAmount(self, remoteAmount: coins, remoteDecimals: uint8): coins { + if (self.hooks.calculateLocalAmount != null) { + return self.hooks.calculateLocalAmount(self.context, remoteAmount, remoteDecimals); // extension hook + } + + if (remoteDecimals == self.data.i_tokenDecimals) { + return remoteAmount; + } + if (remoteDecimals > self.data.i_tokenDecimals) { + val decimalsDiff: uint8 = remoteDecimals - self.data.i_tokenDecimals; + if (decimalsDiff > 77) { + // This is a safety check to prevent overflow in the next calculation. + throw (TokenPool_ERROR_OVERFLOW_DETECTED); + } + // Solidity rounds down so there is no risk of minting more tokens than the remote chain sent. + return remoteAmount / pow(10, decimalsDiff); + } + + // This is a safety check to prevent overflow in the next calculation. + // More than 77 would never fit in a uint256 and would cause an overflow. We also check if the resulting amount + // would overflow. + val diffDecimals: uint8 = self.data.i_tokenDecimals - remoteDecimals; + if (diffDecimals > 77 || remoteAmount > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff /*type(uint256).max*/ / pow(10, diffDecimals)) { + throw (TokenPool_ERROR_OVERFLOW_DETECTED); + } + + return remoteAmount * pow(10, diffDecimals); +} + +// ================================================================ +// │ Chain permissions │ +// ================================================================ +/// @notice Gets the pool address on the remote chain. +/// @param remoteChainSelector Remote chain selector. +/// @dev To support non-evm chains, this value is encoded into bytes +fun TokenPool.getRemotePools(self, remoteChainSelector: int): UMap { + val (remoteChainConfig, ok) = self.data.s_remoteChainConfigs.get(remoteChainSelector); + assert (remoteChainConfig != null) throw TokenPool_ERROR_NON_EXISTENT_CHAIN; + return remoteChainConfig.remotePools; +} + +/// @notice Checks if the pool address is configured on the remote chain. +/// @param remoteChainSelector Remote chain selector. +/// @param remotePoolAddress The address of the remote pool. +fun TokenPool.isRemotePool( + self, + remoteChainSelector: uint64, + remotePoolAddress: CrossChainAddress, +): bool { + return true; +// return Iterator.new(self.data.s_remoteChainConfigs.get(remoteChainSelector).remotePools).has(keccak256(remotePoolAddress)); +} + +/// @notice Gets the token address on the remote chain. +/// @param remoteChainSelector Remote chain selector. +/// @dev To support non-evm chains, this value is encoded into bytes +fun TokenPool.getRemoteToken(self, remoteChainSelector: uint64): CrossChainAddress { + var (remoteChainConfig, ok) = self.data.s_remoteChainConfigs.get(remoteChainSelector); + assert (remoteChainConfig != null) throw TokenPool_ERROR_NON_EXISTENT_CHAIN; + return remoteChainConfig.remoteTokenAddress.load(); +} + +/// @notice Adds a remote pool for a given chain selector. This could be due to a pool being upgraded on the remote +/// chain. We don't simply want to replace the old pool as there could still be valid inflight messages from the old +/// pool. This function allows for multiple pools to be added for a single chain selector. +/// @param remoteChainSelector The remote chain selector for which the remote pool address is being added. +/// @param remotePoolAddress The address of the new remote pool. +fun TokenPool.addRemotePool( + mutate self, + sender: address, + remoteChainSelector: uint64, + remotePoolAddress: CrossChainAddress, +) { + self.ownable.requireOwner(sender); + if (!self.isSupportedChain(remoteChainSelector)) { + throw (TokenPool_ERROR_NON_EXISTENT_CHAIN); + } + + self._setRemotePool(remoteChainSelector, remotePoolAddress); +} + +/// @notice Removes the remote pool address for a given chain selector. +/// @dev All inflight txs from the remote pool will be rejected after it is removed. To ensure no loss of funds, there +/// should be no inflight txs from the given pool. +fun TokenPool.removeRemotePool( + mutate self, + sender: address, + remoteChainSelector: uint64, + remotePoolAddress: slice, +) { + self.ownable.requireOwner(sender); + + if (!self.isSupportedChain(remoteChainSelector)) { + throw (TokenPool_ERROR_NON_EXISTENT_CHAIN); + } + + var (remoteChainConfig, ok) = self.data.s_remoteChainConfigs.get(remoteChainSelector); + assert (remoteChainConfig != null) throw TokenPool_ERROR_NON_EXISTENT_CHAIN; + if (!remoteChainConfig.remotePools.delete(keccak256(remotePoolAddress))) { + throw TokenPool_ERROR_INVALID_REMOTE_POOL_FOR_CHAIN; + } + self.data.s_remoteChainConfigs.set(remoteChainSelector, remoteChainConfig); + + emit(TOKENPOOL_REMOTEPOOLREMOVED_TOPIC, TokenPool_RemotePoolRemoved { remoteChainSelector, remotePoolAddress }); +} + +/// @inheritdoc IPoolV1 +fun TokenPool.isSupportedChain(self, remoteChainSelector: uint64): bool { + return self.data.s_remoteChainSelectors.has(remoteChainSelector); +} + +/// @notice Get list of allowed chains +/// @return list of chains. +fun TokenPool.getSupportedChains(self): UMap { + return self.data.s_remoteChainSelectors; +} + +/// @notice Sets the permissions for a list of chains selectors. Actual senders for these chains +/// need to be allowed on the Router to interact with this pool. +/// @param remoteChainSelectorsToRemove A list of chain selectors to remove. +/// @param chainsToAdd A list of chains and their new permission status & rate limits. Rate limits +/// are only used when the chain is being added through `allowed` being true. +/// @dev Only callable by the owner +fun TokenPool.applyChainUpdates( + mutate self, + sender: address, + chainUpdates: cell, // Contains both remove and add operations +) { + self.ownable.requireOwner(sender); + + // Parse chainUpdates cell to extract remoteChainSelectorsToRemove and chainsToAdd + // This is a simplified version - actual implementation would need proper cell parsing + // TODO: Implement proper parsing of chainUpdates cell + // For now, assume successful operation + +} + +/// @notice Adds a pool address to the allowed remote token pools for a particular chain. +/// @param remoteChainSelector The remote chain selector for which the remote pool address is being added. +/// @param remotePoolAddress The address of the new remote pool. +fun TokenPool._setRemotePool( + mutate self, + remoteChainSelector: uint64, + remotePoolAddress: CrossChainAddress, +) { + // if (remotePoolAddress == createAddressNone()) { // TODO define zero address for CrossChainAddress + // throw (TokenPool_ERROR_ZERO_ADDRESS_INVALID); + // } + + val poolHash: uint256 = keccak256(remotePoolAddress); + + // Check if the pool already exists. + var (remoteChainConfig, ok) = self.data.s_remoteChainConfigs.get(remoteChainSelector); + assert (remoteChainConfig != null) throw (TokenPool_ERROR_NON_EXISTENT_CHAIN); + if (!remoteChainConfig.remotePools.delete(poolHash)) { + throw (TokenPool_ERROR_POOL_ALREADY_ADDED); + } + remoteChainConfig.remotePools.set(poolHash, poolHash); + + self.data.s_remoteChainConfigs.set( + remoteChainSelector, + remoteChainConfig + ); + + // Add the pool to the mapping to be able to un-hash it later. + self.data.s_remotePoolAddresses.set(poolHash, remotePoolAddress); + + emit(TOKENPOOL_REMOTEPOOLADDED_TOPIC, TokenPool_RemotePoolAdded { remoteChainSelector, remotePoolAddress }); +} + +// ================================================================ +// │ Rate limiting │ +// ================================================================ +/// @dev The inbound rate limits should be slightly higher than the outbound rate limits. This is because many chains +/// finalize blocks in batches. CCIP also commits messages in batches: the commit plugin bundles multiple messages in +/// a single merkle root. +/// Imagine the following scenario. +/// - Chain A has an inbound and outbound rate limit of 100 tokens capacity and 1 token per second refill rate. +/// - Chain B has an inbound and outbound rate limit of 100 tokens capacity and 1 token per second refill rate. +/// +/// At time 0: +/// - Chain A sends 100 tokens to Chain B. +/// At time 5: +/// - Chain A sends 5 tokens to Chain B. +/// At time 6: +/// The epoch that contains blocks [0-5] is finalized. +/// Both transactions will be included in the same merkle root and become executable at the same time. This means +/// the token pool on chain B requires a capacity of 105 to successfully execute both messages at the same time. +/// The exact additional capacity required depends on the refill rate and the size of the source chain epochs and the +/// CCIP round time. For simplicity, a 5-10% buffer should be sufficient in most cases. +/// @notice Sets the rate limiter admin address. +/// @dev Only callable by the owner. +/// @param rateLimitAdmin The new rate limiter admin address. +fun TokenPool.setRateLimitAdmin(mutate self, sender: address, rateLimitAdmin: address) { + self.ownable.requireOwner(sender); + self.data.s_rateLimitAdmin = rateLimitAdmin; + emit(TOKENPOOL_RATELIMITADMINSET_TOPIC, TokenPool_RateLimitAdminSet { rateLimitAdmin }); +} + +/// @notice Gets the rate limiter admin address. +fun TokenPool.getRateLimitAdmin(self): address { + return self.data.s_rateLimitAdmin; +} + +/// @notice Consumes outbound rate limiting capacity in this pool +fun TokenPool._consumeOutboundRateLimit( + mutate self, + remoteChainSelector: uint64, + amount: coins, +) { + var (config, ok) = self.data.s_remoteChainConfigs.get(remoteChainSelector); + assert (config != null) throw 1000; + val token = self.data.i_token.load(); + config.outboundRateLimiterConfig._consume(amount, token.masterAddress); + self.data.s_remoteChainConfigs.set(remoteChainSelector, config); + + emit(TOKENPOOL_OUTBOUNDRATELIMITCONSUMED_TOPIC, TokenPool_OutboundRateLimitConsumed { + token: token.masterAddress, + remoteChainSelector, + amount, + }); +} + +/// @notice Consumes inbound rate limiting capacity in this pool +fun TokenPool._consumeInboundRateLimit(mutate self, remoteChainSelector: uint64, amount: coins) { + var (config, ok) = self.data.s_remoteChainConfigs.get(remoteChainSelector); + assert (config != null) throw 1000; + val jetton = lazy self.data.i_token.load(); + config.inboundRateLimiterConfig._consume(amount, jetton.masterAddress); + self.data.s_remoteChainConfigs.set(remoteChainSelector, config); + + emit(TOKENPOOL_INBOUNDRATELIMITCONSUMED_TOPIC, TokenPool_InboundRateLimitConsumed { + token: jetton.masterAddress, + remoteChainSelector, + amount, + }); +} + +/// @notice Gets the token bucket with its values for the block it was requested at. +/// @return The token bucket. +fun TokenPool.getCurrentOutboundRateLimiterState(self, remoteChainSelector: uint64): RateLimiter_TokenBucket { + return self.data.s_remoteChainConfigs.mustGet(remoteChainSelector, TokenPool_ERROR_NON_EXISTENT_CHAIN).outboundRateLimiterConfig + ._currentTokenBucketState(); +} + +/// @notice Gets the token bucket with its values for the block it was requested at. +/// @return The token bucket. +fun TokenPool.getCurrentInboundRateLimiterState(self, remoteChainSelector: uint64): RateLimiter_TokenBucket { + return self.data.s_remoteChainConfigs.mustGet(remoteChainSelector, TokenPool_ERROR_NON_EXISTENT_CHAIN).inboundRateLimiterConfig + ._currentTokenBucketState(); +} + +/// @notice Sets multiple chain rate limiter configs. +/// @param remoteChainSelectors The remote chain selector for which the rate limits apply. +/// @param outboundConfigs The new outbound rate limiter config, meaning the onRamp rate limits for the given chain. +/// @param inboundConfigs The new inbound rate limiter config, meaning the offRamp rate limits for the given chain. +// TODO why not receive this 3 in a single iterator? +fun TokenPool.setChainRateLimiterConfigs( + mutate self, + sender: address, + remoteChainSelectors: Iterator, + outboundConfigs: Iterator, + inboundConfigs: Iterator, +) { + if (sender != self.data.s_rateLimitAdmin) { + self.ownable.requireOwner(sender); + } + if ( + remoteChainSelectors.size(8) != outboundConfigs.size(RATE_LIMITER_CONFIG_LEN) || + remoteChainSelectors.size(8) != inboundConfigs.size(RATE_LIMITER_CONFIG_LEN) + ) { + // revert MismatchedArrayLengths() // TBD should return instead? + throw TokenPool_ERROR_MISMATCHED_ARRAY_LENGTHS; + } + + while (!remoteChainSelectors.empty() && !outboundConfigs.empty() && !inboundConfigs.empty()) { + self._setRateLimitConfig( + remoteChainSelectors.next(), + outboundConfigs.next(), + inboundConfigs.next() + ); + } +} + +/// @notice Sets the chain rate limiter config. +/// @param remoteChainSelector The remote chain selector for which the rate limits apply. +/// @param outboundConfig The new outbound rate limiter config, meaning the onRamp rate limits for the given chain. +/// @param inboundConfig The new inbound rate limiter config, meaning the offRamp rate limits for the given chain. +fun TokenPool.setChainRateLimiterConfig( + mutate self, + sender: address, + remoteChainSelector: uint64, + outboundConfig: RateLimiter_Config, + inboundConfig: RateLimiter_Config, +) { + if (sender != self.s_rateLimitAdmin) { + self.ownable.requireOwner(sender); + } + self._setRateLimitConfig(remoteChainSelector, outboundConfig, inboundConfig); +} + +fun TokenPool._setRateLimitConfig( + mutate self, + remoteChainSelector: uint64, + outboundConfig: RateLimiter_Config, + inboundConfig: RateLimiter_Config, +) { + if (!self.isSupportedChain(remoteChainSelector)) { + throw (TokenPool_ERROR_NON_EXISTENT_CHAIN); + } + RateLimiter._validateTokenBucketConfig(outboundConfig); + { + val config = self.data.s_remoteChainConfigs.get(remoteChainSelector); + config.outboundRateLimiterConfig._setTokenBucketConfig(outboundConfig); + self.data.s_remoteChainConfigs.set(remoteChainSelector, config); + } + RateLimiter._validateTokenBucketConfig(inboundConfig); + { + val config = self.data.s_remoteChainConfigs.get(remoteChainSelector); + config.inboundRateLimiterConfig._setTokenBucketConfig(inboundConfig); + self.data.s_remoteChainConfigs.set(remoteChainSelector, config); + } + emit(TOKENPOOL_CHAINCONFIGURED_TOPIC, TokenPool_ChainConfigured { + remoteChainSelector, + outboundRateLimiterConfig: outboundConfig, + inboundRateLimiterConfig: inboundConfig, + }); +} + +// ================================================================ +// │ Access │ +// ================================================================ +// TODO maybe we should pipe onramp and offramp messages through the token pool +/// @notice Checks whether remote chain selector is configured on this contract, and if the msg.sender +/// is a permissioned onRamp for the given chain on the Router. +/// @dev This function is marked virtual as other token pools may inherit from this contract, but do +/// not receive calls from the ramps directly, instead receiving them from a proxy contract. In that +/// situation this function must be overridden and the ramp-check removed and replaced with a different +/// access-control scheme. +fun TokenPool._onlyOnRamp(self, sender: address, remoteChainSelector: uint64) { +// if (!self.isSupportedChain(remoteChainSelector)) { +// throw (TokenPool_ERROR_CHAIN_NOT_ALLOWED); +// } +// if (!(sender == self.s_router.getOnRamp(remoteChainSelector))) { +// throw (TokenPool_ERROR_CALLER_IS_NOT_A_RAMP_ON_ROUTER); +// } +} + +// /// @notice Checks whether remote chain selector is configured on this contract, and if the msg.sender +// /// is a permissioned offRamp for the given chain on the Router. +// /// @dev This function is marked virtual as other token pools may inherit from this contract, but do +// /// not receive calls from the ramps directly, instead receiving them from a proxy contract. In that +// /// situation this function must be overridden and the ramp-check removed and replaced with a different +// /// access-control scheme. +fun TokenPool._onlyOffRamp(mutate self, sender: address, remoteChainSelector: uint64) { +// if (!self.isSupportedChain(remoteChainSelector)) { +// throw (TokenPool_ERROR_CHAIN_NOT_ALLOWED); +// } +// if (!self.data.s_router.isOffRamp(remoteChainSelector, sender)) { +// throw (TokenPool_ERROR_CALLER_IS_NOT_A_RAMP_ON_ROUTER); +// } +} + +// ================================================================ +// │ Allowlist │ +// ================================================================ +fun TokenPool._checkAllowList(self, sender: address) { + if (self.data.i_allowlistEnabled) { + val (_, ok) = self.data.s_allowlist.get(sender); + if (!ok) { + throw (TokenPool_ERROR_SENDER_NOT_ALLOWED); + } + } +} + +/// @notice Gets whether the allowlist functionality is enabled. +/// @return true is enabled, false if not. +fun TokenPool.getAllowListEnabled(self): bool { + return self.data.i_allowlistEnabled; +} + +/// @notice Gets the allowed addresses. +/// @return The allowed addresses. +fun TokenPool.getAllowList(self): Map { + return self.data.s_allowlist; +} + +/// @notice Apply updates to the allow list. +/// @param removes The addresses to be removed. +/// @param adds The addresses to be added. +fun TokenPool.applyAllowListUpdates( + mutate self, + sender: address, + removes: cell, // Array of addresses to remove + adds: cell, // Array of addresses to add +) { + self.ownable.requireOwner(sender); + self._applyAllowListUpdates( + Iterator
.new(removes), + Iterator
.new(adds), + ); +} + +/// @notice Internal version of applyAllowListUpdates to allow for reuse in the constructor. +fun TokenPool._applyAllowListUpdates( + mutate self, + removes: Iterator
, + adds: Iterator
, +) { + if (!self.data.i_allowlistEnabled) { + throw (TokenPool_ERROR_ALLOW_LIST_NOT_ENABLED); + } + + while (!removes.empty()) { + val toRemove: address = removes.next(); + if (self.data.s_allowlist.delete(toRemove)) { + emit(TOKENPOOL_ALLOWLISTREMOVE_TOPIC, TokenPool_AllowListRemove { sender: toRemove }); + } + } + while (!adds.empty()) { + val toAdd: address = adds.next(); + if (toAdd != createAddressNone() && self.data.s_allowlist.setIfNotExists(toAdd, true)) { + emit(TOKENPOOL_ALLOWLISTADD_TOPIC, TokenPool_AllowListAdd { sender: toAdd }); + } + } +} + + +/// Utils +fun pow(a: int, n: int): int { + var i: int = 0; + var value: int = a; + while (i < n - 1) { + a *= value; + i += 1; + } + return a; +} diff --git a/contracts/contracts/lib/pools/token_pool/errors.tolk b/contracts/contracts/lib/pools/token_pool/errors.tolk new file mode 100644 index 000000000..6f2241131 --- /dev/null +++ b/contracts/contracts/lib/pools/token_pool/errors.tolk @@ -0,0 +1,19 @@ +tolk 1.0 + +const TokenPool_ERROR_CALLER_IS_NOT_A_RAMP_ON_ROUTER = 1001 +const TokenPool_ERROR_ZERO_ADDRESS_INVALID = 1002 +const TokenPool_ERROR_SENDER_NOT_ALLOWED = 1003 +const TokenPool_ERROR_ALLOW_LIST_NOT_ENABLED = 1004 +const TokenPool_ERROR_NON_EXISTENT_CHAIN = 1005 +const TokenPool_ERROR_CHAIN_NOT_ALLOWED = 1006 +const TokenPool_ERROR_CURSED_BY_RMN = 1007 +const TokenPool_ERROR_CHAIN_ALREADY_EXISTS = 1008 +const TokenPool_ERROR_INVALID_SOURCE_POOL_ADDRESS = 1009 +const TokenPool_ERROR_INVALID_TOKEN = 1010 +const TokenPool_ERROR_UNAUTHORIZED = 1011 +const TokenPool_ERROR_POOL_ALREADY_ADDED = 1012 +const TokenPool_ERROR_INVALID_REMOTE_POOL_FOR_CHAIN = 1013 +const TokenPool_ERROR_INVALID_REMOTE_CHAIN_DECIMALS = 1014 +const TokenPool_ERROR_MISMATCHED_ARRAY_LENGTHS = 1015 +const TokenPool_ERROR_OVERFLOW_DETECTED = 1016 +const TokenPool_ERROR_INVALID_DECIMAL_ARGS = 1017 diff --git a/contracts/contracts/lib/pools/token_pool/external_messages.tolk b/contracts/contracts/lib/pools/token_pool/external_messages.tolk new file mode 100644 index 000000000..84770bb64 --- /dev/null +++ b/contracts/contracts/lib/pools/token_pool/external_messages.tolk @@ -0,0 +1,91 @@ +import "../../../ccip/types" +import "../rate_limiter" + +tolk 1.0 + +//crc32("TokenPool_LockedOrBurned") +const TOKENPOOL_LOCKEDORBURNED_TOPIC: int = stringCrc32("TokenPool_LockedOrBurned"); +struct TokenPool_LockedOrBurned { + remoteChainSelector: uint64 + token: address + sender: address + amount: coins +} + +// TBD it is questionable that this types are in the common token pool while they are only used by their respective implementations +//crc32("TokenPool_ReleasedOrMinted") +const TOKENPOOL_RELEASEDORMINTED_TOPIC: int = stringCrc32("TokenPool_ReleasedOrMinted"); +struct TokenPool_ReleasedOrMinted { + remoteChainSelector: uint64 + token: address + sender: address + recipient: address + amount: coins +} +//crc32("TokenPool_ChainAdded") +const TOKENPOOL_CHAINADDED_TOPIC: int = stringCrc32("TokenPool_ChainAdded"); +struct TokenPool_ChainAdded { + remoteChainSelector: uint64 + remoteToken: CrossChainAddress + outboundRateLimiterConfig: RateLimiter_Config + inboundRateLimiterConfig: RateLimiter_Config +} +//crc32("TokenPool_ChainConfigured") +const TOKENPOOL_CHAINCONFIGURED_TOPIC: int = stringCrc32("TokenPool_ChainConfigured"); +struct TokenPool_ChainConfigured { + remoteChainSelector: uint64 + outboundRateLimiterConfig: RateLimiter_Config + inboundRateLimiterConfig: RateLimiter_Config +} +//crc32("TokenPool_ChainRemoved") +const TOKENPOOL_CHAINREMOVED_TOPIC: int = stringCrc32("TokenPool_ChainRemoved"); +struct TokenPool_ChainRemoved { + remoteChainSelector: uint64 +} +//crc32("TokenPool_RemotePoolAdded") +const TOKENPOOL_REMOTEPOOLADDED_TOPIC: int = stringCrc32("TokenPool_RemotePoolAdded"); +struct TokenPool_RemotePoolAdded { + remoteChainSelector: uint64 + remotePoolAddress: slice +} +//crc32("TokenPool_RemotePoolRemoved") +const TOKENPOOL_REMOTEPOOLREMOVED_TOPIC: int = stringCrc32("TokenPool_RemotePoolRemoved"); +struct TokenPool_RemotePoolRemoved { + remoteChainSelector: uint64 + remotePoolAddress: CrossChainAddress +} +//crc32("TokenPool_AllowListAdd") +const TOKENPOOL_ALLOWLISTADD_TOPIC: int = stringCrc32("TokenPool_AllowListAdd"); +struct TokenPool_AllowListAdd { + sender: address +} +//crc32("TokenPool_AllowListRemove") +const TOKENPOOL_ALLOWLISTREMOVE_TOPIC: int = stringCrc32("TokenPool_AllowListRemove"); +struct TokenPool_AllowListRemove { + sender: address +} +//crc32("TokenPool_RouterUpdated") +const TOKENPOOL_ROUTERUPDATED_TOPIC: int = stringCrc32("TokenPool_RouterUpdated"); +struct TokenPool_RouterUpdated { + oldRouter: address + newRouter: address +} +//crc32("TokenPool_RateLimitAdminSet") +const TOKENPOOL_RATELIMITADMINSET_TOPIC: int = stringCrc32("TokenPool_RateLimitAdminSet"); +struct TokenPool_RateLimitAdminSet { + rateLimitAdmin: address +} +//crc32("TokenPool_OutboundRateLimitConsumed") +const TOKENPOOL_OUTBOUNDRATELIMITCONSUMED_TOPIC: int = stringCrc32("TokenPool_OutboundRateLimitConsumed"); +struct TokenPool_OutboundRateLimitConsumed { + remoteChainSelector: uint64 + token: address + amount: coins +} +//crc32("TokenPool_InboundRateLimitConsumed") +const TOKENPOOL_INBOUNDRATELIMITCONSUMED_TOPIC: int = stringCrc32("TokenPool_InboundRateLimitConsumed"); +struct TokenPool_InboundRateLimitConsumed { + remoteChainSelector: uint64 + token: address + amount: coins +} \ No newline at end of file diff --git a/contracts/contracts/lib/pools/token_pool/messages.tolk b/contracts/contracts/lib/pools/token_pool/messages.tolk new file mode 100644 index 000000000..f8e5de379 --- /dev/null +++ b/contracts/contracts/lib/pools/token_pool/messages.tolk @@ -0,0 +1,350 @@ +tolk 1.0 +import "../pool" +import "../rate_limiter" +import "../../../ccip/types" +import "../../utils" + +struct ChainUpdate { + remoteChainSelector: uint64 // Remote chain selector + remotePoolAddresses: Iterator // address of the remote pool, ABI encoded in the case of a remote EVM chain. + remoteTokenAddress: CrossChainAddress // address of the remote token, ABI encoded in the case of a remote EVM chain. + outboundRateLimiterConfig: RateLimiter_Config // Outbound rate limited config, meaning the rate limits for all of the onRamps for the given chain + inboundRateLimiterConfig: RateLimiter_Config // Inbound rate limited config, meaning the rate limits for all of the offRamps for the given chain +} + +/// @dev Union of all incoming messages for TokenPool. +type TokenPool_InMessage = + | TokenPool_IsSupportedToken + | TokenPool_GetToken + | TokenPool_GetRmnProxy + | TokenPool_GetRouter + | TokenPool_SetRouter + // | TokenPool_SupportsInterface // TODO dont know if we even need this + | TokenPool_GetTokenDecimals + | TokenPool_GetRemotePools + | TokenPool_IsRemotePool + | TokenPool_GetRemoteToken + | TokenPool_AddRemotePool + | TokenPool_RemoveRemotePool + | TokenPool_IsSupportedChain + | TokenPool_GetSupportedChains + | TokenPool_ApplyChainUpdates + | TokenPool_SetRateLimitAdmin + | TokenPool_GetRateLimitAdmin + | TokenPool_GetCurrentOutboundRateLimiterState + | TokenPool_GetCurrentInboundRateLimiterState + | TokenPool_GetAllowListEnabled + | TokenPool_GetAllowList + | TokenPool_ApplyAllowListUpdates + +// --- Messages - incoming --- +/// @notice Check if token is supported +struct (0x11223344) TokenPool_IsSupportedToken { + queryId: uint64 + token: address +} + +/// @notice Get the token managed by this pool +struct (0x22334455) TokenPool_GetToken { + queryId: uint64 +} + +/// @notice Get RMN proxy address +struct (0x33445566) TokenPool_GetRmnProxy { + queryId: uint64 +} + +/// @notice Get the router address +struct (0x44556677) TokenPool_GetRouter { + queryId: uint64 +} + +/// @notice Set the router address +struct (0x55667788) TokenPool_SetRouter { + queryId: uint64 + newRouter: address +} + +// TODO dont know if we even need this +/// @notice Check interface support +// struct (0x66778899) TokenPool_SupportsInterface { +// queryId: uint64 +// interfaceId: bytes4 +// } + +/// @notice Lock or burn tokens +struct (0x7788990a) TokenPool_LockOrBurn { + queryId: uint64 + lockOrBurnIn: Pool_LockOrBurnInV1 +} + +/// @notice Release or mint tokens +struct (0x8899aa0b) TokenPool_ReleaseOrMint { + queryId: uint64 + releaseOrMintIn: Pool_ReleaseOrMintInV1 +} + +/// @notice Get token decimals +struct (0x99aabb0c) TokenPool_GetTokenDecimals { + queryId: uint64 +} + +/// @notice Get remote pools for a chain +struct (0xaabbcc0d) TokenPool_GetRemotePools { + queryId: uint64 + remoteChainSelector: uint64 +} + +/// @notice Check if remote pool is valid +struct (0xbbccdd0e) TokenPool_IsRemotePool { + queryId: uint64 + remoteChainSelector: uint64 + remotePoolAddress: CrossChainAddress +} + +/// @notice Get remote token address +struct (0xccddee0f) TokenPool_GetRemoteToken { + queryId: uint64 + remoteChainSelector: uint64 +} + +/// @notice Add remote pool +struct (0xddeeff10) TokenPool_AddRemotePool { + queryId: uint64 + remoteChainSelector: uint64 + remotePoolAddress: CrossChainAddress +} + +/// @notice Remove remote pool +struct (0xeeff0011) TokenPool_RemoveRemotePool { + queryId: uint64 + remoteChainSelector: uint64 + remotePoolAddress: CrossChainAddress +} + +/// @notice Check if chain is supported +struct (0xff001122) TokenPool_IsSupportedChain { + queryId: uint64 + remoteChainSelector: uint64 +} + +/// @notice Get all supported chains +struct (0x00112233) TokenPool_GetSupportedChains { + queryId: uint64 +} + +/// @notice Apply chain configuration updates +struct (0x11223355) TokenPool_ApplyChainUpdates { + queryId: uint64 + chainUpdates: cell // Array of ChainUpdate +} + +/// @notice Set rate limit admin +struct (0x22334466) TokenPool_SetRateLimitAdmin { + queryId: uint64 + rateLimitAdmin: address +} + +/// @notice Get rate limit admin +struct (0x33445577) TokenPool_GetRateLimitAdmin { + queryId: uint64 +} + +/// @notice Get current outbound rate limiter state +struct (0x44556688) TokenPool_GetCurrentOutboundRateLimiterState { + queryId: uint64 + remoteChainSelector: uint64 +} + +/// @notice Get current inbound rate limiter state +struct (0x55667799) TokenPool_GetCurrentInboundRateLimiterState { + queryId: uint64 + remoteChainSelector: uint64 +} + +/// @notice Check if allow list is enabled +struct (0x667788aa) TokenPool_GetAllowListEnabled { + queryId: uint64 +} + +/// @notice Get allow list +struct (0x778899bb) TokenPool_GetAllowList { + queryId: uint64 +} + +/// @notice Apply allow list updates +struct (0x8899aacc) TokenPool_ApplyAllowListUpdates { + queryId: uint64 + removes: cell // Array of addresses to remove + adds: cell // Array of addresses to add +} + +type TokenPool_Response = TokenPool_IsSupportedTokenResponse + | TokenPool_GetTokenResponse + | TokenPool_GetRouterResponse + | TokenPool_SetRouterResponse + // | TokenPool_SupportsInterfaceResponse // TODO dont know if we even need this + | TokenPool_LockOrBurnResponse + | TokenPool_ReleaseOrMintResponse + | TokenPool_GetRmnProxyResponse + | TokenPool_GetTokenDecimalsResponse + | TokenPool_GetRemotePoolsResponse + | TokenPool_IsRemotePoolResponse + | TokenPool_GetRemoteTokenResponse + | TokenPool_AddRemotePoolResponse + | TokenPool_RemoveRemotePoolResponse + | TokenPool_IsSupportedChainResponse + | TokenPool_GetSupportedChainsResponse + | TokenPool_ApplyChainUpdatesResponse + | TokenPool_SetRateLimitAdminResponse + | TokenPool_GetRateLimitAdminResponse + | TokenPool_GetCurrentOutboundRateLimiterStateResponse + | TokenPool_GetCurrentInboundRateLimiterStateResponse + | TokenPool_GetAllowListEnabledResponse + | TokenPool_GetAllowListResponse + | TokenPool_ApplyAllowListUpdatesResponse + +// --- Messages - outgoing --- +/// @notice Response for IsSupportedToken query +struct (0xa1a2a3a4) TokenPool_IsSupportedTokenResponse { + queryId: uint64 + isSupported: bool +} + +/// @notice Response for GetToken query +struct (0xa2a3a4a5) TokenPool_GetTokenResponse { + queryId: uint64 + token: address +} + +/// @notice Response for GetRmnProxy query +struct (0xa3a4a5a6) TokenPool_GetRmnProxyResponse { + queryId: uint64 + rmnProxy: address +} + +/// @notice Response for GetRouter query +struct (0xa4a5a6a7) TokenPool_GetRouterResponse { + queryId: uint64 + router: address +} + +/// @notice Response for SetRouter operation +struct (0xa5a6a7a8) TokenPool_SetRouterResponse { + queryId: uint64 +} + +// TODO dont know if we even need this +/// @notice Response for SupportsInterface query +// struct (0xa6a7a8a9) TokenPool_SupportsInterfaceResponse { +// queryId: uint64 +// supported: bool +// } + +/// @notice Response for LockOrBurn operation +struct (0xa7a8a9aa) TokenPool_LockOrBurnResponse { + queryId: uint64 + result: Pool_LockOrBurnOutV1 +} + +/// @notice Response for ReleaseOrMint operation +struct (0xa8a9aabb) TokenPool_ReleaseOrMintResponse { + queryId: uint64 + result: Pool_ReleaseOrMintOutV1 +} + +/// @notice Response for GetTokenDecimals query +struct (0xa9aabbcc) TokenPool_GetTokenDecimalsResponse { + queryId: uint64 + decimals: uint8 +} + +/// @notice Response for GetRemotePools query +struct (0xaabbccdd) TokenPool_GetRemotePoolsResponse { + queryId: uint64 + remotePools: UMap +} + +/// @notice Response for IsRemotePool query +struct (0xabbccdee) TokenPool_IsRemotePoolResponse { + queryId: uint64 + isRemotePool: bool +} + +/// @notice Response for GetRemoteToken query +struct (0xbccddeef) TokenPool_GetRemoteTokenResponse { + queryId: uint64 + remoteToken: CrossChainAddress +} + +/// @notice Response for AddRemotePool operation +struct (0xcddeeffe) TokenPool_AddRemotePoolResponse { + queryId: uint64 + success: bool +} + +/// @notice Response for RemoveRemotePool operation +struct (0xddeeffff) TokenPool_RemoveRemotePoolResponse { + queryId: uint64 + success: bool +} + +/// @notice Response for IsSupportedChain query +struct (0xeeff0000) TokenPool_IsSupportedChainResponse { + queryId: uint64 + isSupported: bool +} + +/// @notice Response for GetSupportedChains query +struct (0xff000011) TokenPool_GetSupportedChainsResponse { + queryId: uint64 + supportedChains: UMap +} + +/// @notice Response for ApplyChainUpdates operation +struct (0x00001122) TokenPool_ApplyChainUpdatesResponse { + queryId: uint64 + success: bool +} + +/// @notice Response for SetRateLimitAdmin operation +struct (0x00112233) TokenPool_SetRateLimitAdminResponse { + queryId: uint64 + success: bool +} + +/// @notice Response for GetRateLimitAdmin query +struct (0x01122334) TokenPool_GetRateLimitAdminResponse { + queryId: uint64 + rateLimitAdmin: address +} + +/// @notice Response for GetCurrentOutboundRateLimiterState query +struct (0x02233445) TokenPool_GetCurrentOutboundRateLimiterStateResponse { + queryId: uint64 + state: RateLimiter_TokenBucket +} + +/// @notice Response for GetCurrentInboundRateLimiterState query +struct (0x03344556) TokenPool_GetCurrentInboundRateLimiterStateResponse { + queryId: uint64 + state: RateLimiter_TokenBucket +} + +/// @notice Response for GetAllowListEnabled query +struct (0x04455667) TokenPool_GetAllowListEnabledResponse { + queryId: uint64 + enabled: bool +} + +/// @notice Response for GetAllowList query +struct (0x05566778) TokenPool_GetAllowListResponse { + queryId: uint64 + allowList: Map +} + +/// @notice Response for ApplyAllowListUpdates operation +struct (0x06677889) TokenPool_ApplyAllowListUpdatesResponse { + queryId: uint64 + success: bool +} diff --git a/contracts/contracts/lib/pools/token_pool/storage.tolk b/contracts/contracts/lib/pools/token_pool/storage.tolk new file mode 100644 index 000000000..5e2b61d41 --- /dev/null +++ b/contracts/contracts/lib/pools/token_pool/storage.tolk @@ -0,0 +1,58 @@ +tolk 1.0 + +import "../pool" +import "../rate_limiter" +import "../../jetton/jetton_client" +import "../../utils" +import "../../../ccip/types" + + +struct RemoteChainConfig { + outboundRateLimiterConfig: RateLimiter_TokenBucket // Outbound rate limited config, meaning the rate limits for all of the onRamps for the given chain + inboundRateLimiterConfig: RateLimiter_TokenBucket // Inbound rate limited config, meaning the rate limits for all of the offRamps for the given chain + remoteTokenAddress: Cell // address of the remote token, ABI encoded in the case of a remote EVM chain. + remotePools: UMap // vec<256> // Set of remote pool hashes, ABI encoded in the case of a remote EVM chain. // Original implementation uses bytes32 but in tolk keccak returns 256 +} + + +struct TokenPool_Data { + /// @dev The bridgeable token that is managed by this pool. Pools could support multiple tokens at the same time if + /// required, but this implementation only supports one token. + i_token: Cell, + /// @dev The number of decimals of the token managed by this pool. + i_tokenDecimals: uint8 + /// @dev The address of the RMN proxy + // i_rmnProxy: address + /// @dev The immutable flag that indicates if the pool is access-controlled. + i_allowlistEnabled: bool + /// @dev A set of addresses allowed to trigger lockOrBurn as original senders. + /// Only takes effect if i_allowlistEnabled is true. + /// This can be used to ensure only token-issuer specified addresses can move tokens. + s_allowlist: Map + /// @dev The address of the router + s_router: address + /// @dev A set of allowed chain selectors. We want the allowlist to be enumerable to + /// be able to quickly determine (without parsing logs) who can access the pool. + /// @dev The chain selectors are in uint256 format because of the EnumerableSet implementation. + s_remoteChainSelectors: UMap + // mapping(uint64 remoteChainSelector => RemoteChainConfig) internal s_remoteChainConfigs + s_remoteChainConfigs: UMap + /// @notice A mapping of hashed pool addresses to their unhashed form. This is used to be able to find the actually + /// configured pools and not just their hashed versions. + // mapping(bytes32 poolAddressHash => bytes poolAddress) internal s_remotePoolAddresses + s_remotePoolAddresses: UMap + /// @notice The address of the rate limiter admin. + /// @dev Can be address(0) if none is configured. + s_rateLimitAdmin: address // TBD use owner instead? +} + +/// Load from cell using auto-serialization. +fun TokenPool_Data.fromCell(data: cell): TokenPool_Data { + return TokenPool_Data.fromCell(data); +} + +/// Store as cell using auto-serialization. +@inline +fun TokenPool_Data.asCell(self): cell { + return self.toCell(); +} \ No newline at end of file diff --git a/contracts/contracts/pools/lock_release_token_pool.tolk b/contracts/contracts/pools/lock_release_token_pool.tolk new file mode 100644 index 000000000..9e183f5ce --- /dev/null +++ b/contracts/contracts/pools/lock_release_token_pool.tolk @@ -0,0 +1,281 @@ +import "../lib/pools/token_pool" +import "../lib/pools/pool" +import "../lib/pools/ipool" +import "../lib/pools/token_pool/external_messages" +import "../lib/pools/token_pool/storage" +import "../lib/access/ownable_2step" +import "../lib/utils" +import "../lib/jetton/jetton_client" + +import "./lock_release_token_pool/storage" +import "./lock_release_token_pool/messages" +import "./lock_release_token_pool/errors" +import "./lock_release_token_pool/external_messages" + +tolk 1.0 + +// import {IERC20} from +// "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +// import {SafeERC20} from +// "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; +/// @notice Token pool used for tokens on their native chain. This uses a lock and release mechanism. +/// Because of lock/unlock requiring liquidity, this pool contract also has function to add and remove +/// liquidity. This allows for proper bookkeeping for both user and liquidity provider balances. +/// @dev One token per LockReleaseTokenPool. + +struct LockReleaseTokenPool { + data: LockReleaseTokenPool_Data +} + +// --- Methods --- +/// @dev Loads the LockReleaseTokenPool contract data from persistent storage. +fun LockReleaseTokenPool.load(): LockReleaseTokenPool { + // Load the contract data from the persistent storage + var data = LockReleaseTokenPool_Data.fromContractData(); + + // Return the LockReleaseTokenPool instance with loaded data and hooks + return LockReleaseTokenPool { + data, + }; +} + +fun LockReleaseTokenPool.tokenPool(self): TokenPool { + val tokenPoolData = lazy self.data.tokenPoolData.load(); + + return TokenPool{ + data: tokenPoolData, + ownable: self.data.ownable, + context: null, + hooks: TokenPool_Hooks { + getRouter: null, + _parseRemoteDecimals: null, + getTokenDecimals: null, + encodeLocalDecimals: null, + calculateLocalAmount: null, + } + }; +} + +// TODO: handle bounced messages +fun onInternalMessage(in: InMessage) { + /// Load the contract storage as MCMS and handle the message + var lockReleaseTokenPool = LockReleaseTokenPool.load(); + + var tokenPool = lockReleaseTokenPool.tokenPool(); + + if (tokenPool.onInternalMessage(in.senderAddress, in.valueCoins, in.body)) { + lockReleaseTokenPool.data.tokenPoolData = tokenPool.data.toCell(); + lockReleaseTokenPool.data.storeAsContractData(); + return; + } + + lockReleaseTokenPool.onInternalMessage(in.senderAddress, in.valueCoins, in.body); +} + + +// --- Message handlers --- + + +fun LockReleaseTokenPool.onInternalMessage(mutate self, sender: address, msgValue: coins, msgBody: slice) { + var response: LockReleaseTokenPool_Response? = null; + val msg = lazy LockReleaseTokenPool_InMessage.fromSlice(msgBody); + match (msg) { + Pool_LockOrBurnInV1 => { + response = lockOrBurn(mutate self, sender, msgValue, msg); + } + Pool_ReleaseOrMintInV1 => { + response = releaseOrMint(mutate self, sender, msgValue, msg); + } + LockReleaseTokenPool_GetRebalancer => { + val rebalancer = self.handleGetRebalancer(sender); + response = LockReleaseTokenPool_GetRebalancerResponse { + queryId: msg.queryId, + rebalancer: self.getRebalancer(), + }; + } + LockReleaseTokenPool_SetRebalancer => { + self.handleSetRebalancer(sender, msg.rebalancer); + response = LockReleaseTokenPool_SetRebalancerResponse { + queryId: msg.queryId, + }; + } + // LockReleaseTokenPool_ProvideLiquidity => self.handleProvideLiquidity(sender), + // LockReleaseTokenPool_WithdrawLiquidity => self.handleWithdrawLiquidity(sender), + // LockReleaseTokenPool_TransferLiquidity => self.handleTransferLiquidity(sender), + else => { + // TODO assert (msgBody.isEmpty()) throw 0xFFFF + return; + } + } + + + createMessage({ + bounce: false, + value: 0, + dest: sender, + body: response, + }).send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); + + /// Store the updated contract data + self.data.storeAsContractData() +} + +// --- Internal functions --- + +// ================================================================ +// │ Lock or Burn │ +// ================================================================ +/// @notice Burn the token in the pool +/// @dev The _validateLockOrBurn check is an essential security check +fun lockOrBurn(mutate this: LockReleaseTokenPool, sender: address, msgValue: coins, lockOrBurnIn: Pool_LockOrBurnInV1): Pool_LockOrBurnOutV1 { + this.tokenPool()._validateLockOrBurn(sender, lockOrBurnIn); + + val token = lazy this.tokenPool().data.i_token.load(); + emit(TOKENPOOL_LOCKEDORBURNED_TOPIC, TokenPool_LockedOrBurned { + remoteChainSelector: lockOrBurnIn.remoteChainSelector, + token: token.masterAddress, + sender, + amount: lockOrBurnIn.amount, + }); + + return Pool_LockOrBurnOutV1 { + msg: lockOrBurnIn.msg, + destTokenAddress: this.tokenPool().getRemoteToken(lockOrBurnIn.remoteChainSelector), + destPoolData: this.tokenPool()._encodeLocalDecimals(), + }; +} + +// ================================================================ +// │ Release or Mint │ +// ================================================================ +/// @notice Mint tokens from the pool to the recipient +/// @dev The _validateReleaseOrMint check is an essential security check +fun releaseOrMint(mutate this: LockReleaseTokenPool, sender: address, msgValue: coins, releaseOrMintIn: Pool_ReleaseOrMintInV1): Pool_ReleaseOrMintOutV1 { + // Calculate the local amount + val localAmount = this.tokenPool()._calculateLocalAmount( + releaseOrMintIn.sourceDenominatedAmount, + this.tokenPool()._parseRemoteDecimals(releaseOrMintIn.sourcePoolData), + ); + + // TODO revisit. This is the other way arround in the original contracts. + this.tokenPool()._validateReleaseOrMint(releaseOrMintIn, localAmount); + + + val ccipSend = lazy releaseOrMintIn.msg.load(); + + // Mint to the receiver + this.tokenPool().data.i_token.load().sendSimple( + JettonMessageOptions { + bounce: true, + value: 0, + }, + SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE, + ccipSend.queryId, + localAmount, + releaseOrMintIn.receiver, + contract.getAddress(), // receive transfer confirmation and recover extra TON + ); + + // TODO this should be emitted when we receive the transfer receipt + val token = lazy this.tokenPool().data.i_token.load(); + emit(TOKENPOOL_RELEASEDORMINTED_TOPIC, TokenPool_ReleasedOrMinted { + remoteChainSelector: releaseOrMintIn.remoteChainSelector, + token: token.masterAddress, + sender: sender, + recipient: releaseOrMintIn.receiver, + amount: localAmount, + }); + + return Pool_ReleaseOrMintOutV1{ msg: releaseOrMintIn.msg, destinationAmount: localAmount }; +} + +fun LockReleaseTokenPool.handleGetRebalancer(self, sender: address): address { + return self.getRebalancer(); +} + +/// @notice Gets rebalancer, can be address(0) if none is configured. +/// @return The current liquidity manager. +fun LockReleaseTokenPool.getRebalancer(self): address { + return self.data.s_rebalancer; +} + +fun LockReleaseTokenPool.handleSetRebalancer(mutate self, sender: address, rebalancer: address) { + return self.setRebalancer(sender, rebalancer); +} + +/// @notice Sets the rebalancer address. +/// @dev Address(0) can be used to disable the rebalancer. +/// @dev Only callable by the owner. +fun LockReleaseTokenPool.setRebalancer(mutate self, sender: address, rebalancer: address) { + self.tokenPool().ownable.requireOwner(sender); + val oldRebalancer: address = self.data.s_rebalancer; + + self.data.s_rebalancer = rebalancer; + + emit(LOCK_RELEASE_TOKEN_POOL_REBALANCER_SET_TOPIC, LockReleaseTokenPool_RebalancerSet{ oldRebalancer, newRebalancer: rebalancer }); +} + +/// @notice Adds liquidity to the pool. The tokens should be approved first. +/// @param amount The amount of liquidity to provide. +// fun LockReleaseTokenPool.provideLiquidity(mutate self, sender: address, amount: uint256): bool { +// try { +// if (self.data.s_rebalancer != sender) { +// throw TokenPool_ERROR_UNAUTHORIZED; +// } + +// self.data.tokenPool.i_token.safeTransferFrom(sender, contract.getAddress(), amount); +// emit(LOCK_RELEASE_TOKEN_POOL_LIQUIDITY_ADDED_TOPIC, LiquidityAdded { provider: sender, amount }); +// return true; +// } catch { +// return false; +// } +// } + +/// @notice Removes liquidity to the pool +/// @param amount The amount of liquidity to remove. +// fun LockReleaseTokenPool.withdrawLiquidity(mutate self, sender: address, amount: uint256): bool { +// try { +// if (self.data.s_rebalancer != sender) { +// throw TokenPool_ERROR_UNAUTHORIZED; +// } + +// self.data.tokenPool.i_token.safeTransfer(sender, amount); +// emit(LOCK_RELEASE_TOKEN_POOL_LIQUIDITY_REMOVED_TOPIC, LiquidityRemoved { provider: sender, amount }); +// return true; +// } catch { +// return false; +// } +// } + +/// @notice This function can be used to transfer liquidity from an older version of the pool to this pool. To do so +/// this pool will have to be set as the rebalancer in the older version of the pool. This allows it to transfer the +/// funds in the old pool to the new pool. +/// @dev When upgrading a LockRelease pool, this function can be called at the same time as the pool is changed in the +/// TokenAdminRegistry. This allows for a smooth transition of both liquidity and transactions to the new pool. +/// Alternatively, when no multicall is available, a portion of the funds can be transferred to the new pool before +/// changing which pool CCIP uses, to ensure both pools can operate. Then the pool should be changed in the +/// TokenAdminRegistry, which will activate the new pool. All new transactions will use the new pool and its +/// liquidity. Finally, the remaining liquidity can be transferred to the new pool using this function one more time. +/// @param from The address of the old pool. +/// @param amount The amount of liquidity to transfer. If uint256.max is passed, all liquidity will be transferred. +// fun LockReleaseTokenPool.transferLiquidity( +// mutate self, +// sender: address, +// from: address, +// amount: uint256, +// ) { +// self.data.tokenPool.ownable().requireOwner(sender); +// var transferAmount = amount; +// if (amount == 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) { // type(uint256).max +// transferAmount = self.data.tokenPool.i_token.balanceOf(from); +// } + +// LockReleaseTokenPool(from).withdrawLiquidity(sender, transferAmount); + +// emit(LiquidityTransferred({ from, amount: transferAmount })); +// } + +get fun getRebalancer(): address { + val this = LockReleaseTokenPool.load(); + return this.data.s_rebalancer; +} \ No newline at end of file diff --git a/contracts/contracts/pools/lock_release_token_pool/errors.tolk b/contracts/contracts/pools/lock_release_token_pool/errors.tolk new file mode 100644 index 000000000..278d94cd0 --- /dev/null +++ b/contracts/contracts/pools/lock_release_token_pool/errors.tolk @@ -0,0 +1,4 @@ +tolk 1.0 + +const ERROR_INSUFFICIENT_LIQUIDITY = 1001 + diff --git a/contracts/contracts/pools/lock_release_token_pool/external_messages.tolk b/contracts/contracts/pools/lock_release_token_pool/external_messages.tolk new file mode 100644 index 000000000..dda0cb823 --- /dev/null +++ b/contracts/contracts/pools/lock_release_token_pool/external_messages.tolk @@ -0,0 +1,33 @@ +tolk 1.0 + +// Event +//crc32("LockReleaseTokenPool_RebalancerSet") +const LOCK_RELEASE_TOKEN_POOL_REBALANCER_SET_TOPIC: int = stringCrc32("LockReleaseTokenPool_RebalancerSet"); +struct LockReleaseTokenPool_RebalancerSet { + oldRebalancer: address + newRebalancer: address +} + +// Event +//crc32("LockReleaseTokenPool_LiquidityAdded") +const LOCK_RELEASE_TOKEN_POOL_LIQUIDITY_ADDED_TOPIC: int = stringCrc32("LockReleaseTokenPool_LiquidityAdded"); +struct LockReleaseTokenPool_LiquidityAdded { + provider: address + amount: uint256 +} + +// Event +//crc32("LockReleaseTokenPool_LiquidityRemoved") +const LOCK_RELEASE_TOKEN_POOL_LIQUIDITY_REMOVED_TOPIC: int = stringCrc32("LockReleaseTokenPool_LiquidityRemoved"); +struct LockReleaseTokenPool_LiquidityRemoved { + provider: address + amount: uint256 +} + +// Event +//crc32("LockReleaseTokenPool_LiquidityTransferred") +const LOCK_RELEASE_TOKEN_POOL_LIQUIDITY_TRANSFERRED_TOPIC: int = stringCrc32("LockReleaseTokenPool_LiquidityTransferred"); +struct LockReleaseTokenPool_LiquidityTransferred { + from: address + amount: uint256 +} \ No newline at end of file diff --git a/contracts/contracts/pools/lock_release_token_pool/messages.tolk b/contracts/contracts/pools/lock_release_token_pool/messages.tolk new file mode 100644 index 000000000..b9cf11769 --- /dev/null +++ b/contracts/contracts/pools/lock_release_token_pool/messages.tolk @@ -0,0 +1,87 @@ +tolk 1.0 + +import "../../lib/pools/pool" +import "../../lib/pools/ipool" + +// --- Messages - incoming --- +/// @notice Gets rebalancer address +struct (0x1a2b3c4d) LockReleaseTokenPool_GetRebalancer { + queryId: uint64 +} + +/// @notice Sets the rebalancer address. +/// @dev Address(0) can be used to disable the rebalancer. +/// @dev Only callable by the owner. +struct (0x2b3c4d5e) LockReleaseTokenPool_SetRebalancer { + queryId: uint64 + rebalancer: address +} + +/// @notice Adds liquidity to the pool. The tokens should be approved first. +/// @param amount The amount of liquidity to provide. +struct (0x3c4d5e6f) LockReleaseTokenPool_ProvideLiquidity { + queryId: uint64 + amount: uint256 +} + +/// @notice Removed liquidity to the pool. The tokens will be sent to msg.sender. +/// @param amount The amount of liquidity to remove. +struct (0x4d5e6f70) LockReleaseTokenPool_WithdrawLiquidity { + queryId: uint64 + amount: uint256 +} + +/// @notice Transfer liquidity from an older version of the pool to this pool. +/// @param from The address of the old pool. +/// @param amount The amount of liquidity to transfer. If uint256.max is passed, all liquidity will be transferred. +struct (0x5e6f7081) LockReleaseTokenPool_TransferLiquidity { + queryId: uint64 + from: address + amount: uint256 +} + +/// @dev Union of all incoming messages. +type LockReleaseTokenPool_InMessage = + | Pool_LockOrBurnInV1 + | Pool_ReleaseOrMintInV1 + | LockReleaseTokenPool_GetRebalancer + | LockReleaseTokenPool_SetRebalancer + // | LockReleaseTokenPool_ProvideLiquidity + // | LockReleaseTokenPool_WithdrawLiquidity + // | LockReleaseTokenPool_TransferLiquidity + +// --- Messages - outgoing --- +/// @notice Response for GetRebalancer query +struct (0xa1b2c3d4) LockReleaseTokenPool_GetRebalancerResponse { + queryId: uint64 + rebalancer: address +} + +/// @notice Confirmation for SetRebalancer operation +struct (0xb2c3d4e5) LockReleaseTokenPool_SetRebalancerResponse { + queryId: uint64 +} + +/// @notice Confirmation for ProvideLiquidity operation +struct (0xc3d4e5f6) LockReleaseTokenPool_ProvideLiquidityResponse { + queryId: uint64 +} + +/// @notice Confirmation for WithdrawLiquidity operation +struct (0xd4e5f607) LockReleaseTokenPool_WithdrawLiquidityResponse { + queryId: uint64 +} + +/// @notice Confirmation for TransferLiquidity operation +struct (0xe5f60718) LockReleaseTokenPool_TransferLiquidityResponse { + queryId: uint64 +} + +type LockReleaseTokenPool_Response = + | Pool_LockOrBurnOutV1 + | Pool_ReleaseOrMintOutV1 + | LockReleaseTokenPool_GetRebalancerResponse + | LockReleaseTokenPool_SetRebalancerResponse + | LockReleaseTokenPool_ProvideLiquidityResponse + | LockReleaseTokenPool_WithdrawLiquidityResponse + | LockReleaseTokenPool_TransferLiquidityResponse \ No newline at end of file diff --git a/contracts/contracts/pools/lock_release_token_pool/storage.tolk b/contracts/contracts/pools/lock_release_token_pool/storage.tolk new file mode 100644 index 000000000..e44439bd8 --- /dev/null +++ b/contracts/contracts/pools/lock_release_token_pool/storage.tolk @@ -0,0 +1,29 @@ +tolk 1.0 + +import "../../lib/pools/token_pool/storage" +import "../../lib/access/ownable_2step" + +/// @dev LockReleaseTokenPool contract struct with optional hooks for virtual functions +struct LockReleaseTokenPool_Data { + /// ID allows multiple independent instances + id: uint32 + /// @notice The address of the rebalancer. + s_rebalancer: address + + /// Ownable trait data + ownable: Ownable2Step, + + tokenPoolData: Cell +} + +/// Load from contract data using auto-serialization. +@inline +fun LockReleaseTokenPool_Data.fromContractData(): LockReleaseTokenPool_Data { + return LockReleaseTokenPool_Data.fromCell(contract.getData()) +} + +/// Store as contract data into persistent storage using auto-serialization. +@inline +fun LockReleaseTokenPool_Data.storeAsContractData(self) { + contract.setData(self.toCell()); +} diff --git a/contracts/tests/pools/BaseTest.ts b/contracts/tests/pools/BaseTest.ts new file mode 100644 index 000000000..2a6a48cd8 --- /dev/null +++ b/contracts/tests/pools/BaseTest.ts @@ -0,0 +1,316 @@ +import '@ton/test-utils' + +import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox' +import { Cell, toNano, beginCell, Address } from '@ton/core' +import { compile } from '@ton/blueprint' + +import { crc32 } from 'zlib' + +import { + JettonMinter, + jettonMinterConfigToCell, + JettonMinterContent, +} from '../../wrappers/jetton/JettonMinter' +import { JettonWallet } from '../../wrappers/jetton/JettonWallet' +import { lockReleaseTokenPool } from '../../wrappers/pools' +import { env } from 'process' +import { readFileSync } from 'fs' + +export type TestCode = { + lockReleaseTokenPool: Cell + jettonMinter: Cell + jettonWallet: Cell +} + +export type TestAccounts = { + deployer: SandboxContract + owner: SandboxContract + stranger: SandboxContract + ramp: SandboxContract + allowedSender1: SandboxContract + allowedSender2: SandboxContract + rebalancer: SandboxContract + liquidityProvider: SandboxContract +} + +export type TestContracts = { + lockReleaseTokenPool: SandboxContract + jettonMinter: SandboxContract + jettonWallet: SandboxContract +} + +export class BaseTokenPoolTest { + // Constants from Solidity BaseTest + static readonly BLOCK_TIME = 1n + static readonly TWELVE_HOURS = 60n * 60n * 12n + + // Chain selectors + static readonly SOURCE_CHAIN_SELECTOR = 1n + static readonly DEST_CHAIN_SELECTOR = 2n + + // Gas and token defaults + static readonly GAS_LIMIT = 200_000n + static readonly DEFAULT_TOKEN_DEST_GAS_OVERHEAD = 90_000n + static readonly DEFAULT_TOKEN_DECIMALS = 18 + static readonly GAS_FOR_CALL_EXACT_CHECK = 5_000n + + // Rate limiter configs - equivalent to Solidity BaseTest + static readonly OUTBOUND_RATE_LIMITER_CONFIG = { + isEnabled: true, + capacity: 100n * 10n ** 28n, // 100e28 + rate: 10n ** 15n, // 1e15 + } + + static readonly INBOUND_RATE_LIMITER_CONFIG = { + isEnabled: true, + capacity: 222n * 10n ** 30n, // 222e30 + rate: 10n ** 18n, // 1e18 + } + + blockchain: Blockchain + code: TestCode + acc: TestAccounts + bind: TestContracts + + constructor() { + this.blockchain = null as any + this.code = null as any + this.acc = null as any + this.bind = null as any + } + + static async compileContracts(): Promise { + return { + lockReleaseTokenPool: await compile('pools.LockReleaseTokenPool'), + jettonMinter: await JettonMinterCode(), + jettonWallet: await JettonWalletCode(), + } + } + + /** + * Initialize the blockchain and setup test accounts + */ + async initializeBlockchain(): Promise { + this.blockchain = await Blockchain.create() + this.blockchain.now = Number(BaseTokenPoolTest.BLOCK_TIME) + this.blockchain.verbosity = { + print: true, + blockchainLogs: false, + vmLogs: 'none', + debugLogs: true, + } + + // Set up accounts following Solidity BaseTest pattern + this.acc = { + deployer: await this.blockchain.treasury('deployer'), + owner: await this.blockchain.treasury('owner'), + stranger: await this.blockchain.treasury('stranger'), + ramp: await this.blockchain.treasury('ramp'), + allowedSender1: await this.blockchain.treasury('allowedSender1'), + allowedSender2: await this.blockchain.treasury('allowedSender2'), + rebalancer: await this.blockchain.treasury('rebalancer'), + liquidityProvider: await this.blockchain.treasury('liquidityProvider'), + } + + this.bind = { + lockReleaseTokenPool: null as any, + jettonMinter: null as any, + jettonWallet: null as any, + } + } + + /** + * Setup a basic ERC20-like jetton for testing + */ + async setupJettonMinter( + testId: string, + decimals: number = BaseTokenPoolTest.DEFAULT_TOKEN_DECIMALS, + ): Promise { + // Create jetton content similar to the working Jetton.spec.ts + const jettonContent = beginCell().storeStringTail('smartcontract.com').endCell() + this.bind.jettonMinter = this.blockchain.openContract( + JettonMinter.createFromConfig( + { + admin: this.acc.owner.address, + walletCode: this.code.jettonWallet, + jettonContent, + totalSupply: 0n, + }, + this.code.jettonMinter, + ), + ) + } + + /** + * Deploy the jetton minter and verify deployment + */ + async deployJettonMinter(): Promise { + const deployResult = await this.bind.jettonMinter.sendDeploy( + this.acc.deployer.getSender(), + toNano('1'), + ) + + expect(deployResult.transactions).toHaveTransaction({ + from: this.acc.deployer.address, + to: this.bind.jettonMinter.address, + deploy: true, + }) + + // Verify initial state + const jettonData = await this.bind.jettonMinter.getJettonData() + expect(jettonData.totalSupply).toEqual(0n) // Initially 0, as set in config + expect(jettonData.admin).toEqualAddress(this.acc.owner.address) + } + + /** + * Setup a mock RMN (Risk Management Network) contract + */ + async setupMockRMN(testId: string): Promise { + // TODO: Implement when RMN wrapper is available + // For now, create a minimal mock that always returns "not cursed" + console.warn('Mock RMN setup not yet implemented - waiting for RMN wrapper') + } + + /** + * Setup a mock Router contract + */ + async setupMockRouter(testId: string): Promise { + // TODO: Implement when Router wrapper is available + console.warn('Mock Router setup not yet implemented - waiting for Router wrapper') + } + + /** + * Create rate limiter config data structure + */ + createRateLimiterConfig(isEnabled: boolean, capacity: bigint, rate: bigint): any { + return { + isEnabled, + capacity, + rate, + } + } + + /** + * Get default outbound rate limiter config + */ + getOutboundRateLimiterConfig(): any { + return BaseTokenPoolTest.OUTBOUND_RATE_LIMITER_CONFIG + } + + /** + * Get default inbound rate limiter config + */ + getInboundRateLimiterConfig(): any { + return BaseTokenPoolTest.INBOUND_RATE_LIMITER_CONFIG + } + + /** + * Create a token price update structure (equivalent to Solidity _getSingleTokenPriceUpdateStruct) + */ + createSingleTokenPriceUpdate(token: Address, price: bigint): any { + return { + tokenPriceUpdates: [ + { + sourceToken: token, + usdPerToken: price, + }, + ], + } + } + + /** + * Generate source token data (equivalent to Solidity _generateSourceTokenData) + */ + generateSourceTokenData(): any { + return { + destGasAmount: BaseTokenPoolTest.DEFAULT_TOKEN_DEST_GAS_OVERHEAD, + } + } + + /** + * Set mock RMN chain curse status + */ + setMockRMNChainCurse(chainSelector: bigint, isCursed: boolean): void { + // TODO: Implement when RMN mock is available + console.warn(`Setting chain ${chainSelector} curse status to ${isCursed} - not yet implemented`) + } + + /** + * Warp blockchain time forward by specified seconds + */ + warpTime(seconds: number): void { + this.blockchain.now = this.blockchain.now!! + seconds + } + + /** + * Complete basic setup for token pool tests + */ + async setupBasics(testId: string): Promise { + await this.initializeBlockchain() + await this.setupJettonMinter(testId) + await this.deployJettonMinter() + await this.setupMockRMN(testId) + await this.setupMockRouter(testId) + } + + /** + * Helper to create makeAddr equivalent + */ + async makeAddr(name: string): Promise
{ + const treasury = await this.blockchain.treasury(name) + return treasury.address + } + + /** + * Helper to deal tokens to an address (mint jettons) + */ + async dealTokens(to: Address, amount: bigint): Promise { + // Mint jettons to the specified address + const mintResult = await this.bind.jettonMinter.sendMint(this.acc.owner.getSender(), { + value: toNano('0.05'), // TON for gas + message: { + queryId: 1n, + destination: to, + tonAmount: toNano('0.02'), // TON amount for the recipient + jettonAmount: amount, + from: this.acc.owner.address, + responseDestination: this.acc.owner.address, + customPayload: null, + forwardTonAmount: 0n, + }, + }) + + expect(mintResult.transactions).toHaveTransaction({ + from: this.acc.owner.address, + success: true, + }) + } +} + +const PATH_CONTRACTS_JETTON = env.PATH_CONTRACTS_JETTON + +async function JettonMinterCode(): Promise { + const compiledPath = `${PATH_CONTRACTS_JETTON}/JettonMinter.compiled.json` + const compiled = JSON.parse(readFileSync(compiledPath, 'utf8')) + const hex = compiled.hex + if (!hex) { + throw new Error('Compiled JettonMinter code hex not found in JSON') + } + // Remove 0x prefix if present + const hexStr = hex.startsWith('0x') ? hex.slice(2) : hex + const boc = Buffer.from(hexStr, 'hex') + return Cell.fromBoc(boc)[0] +} + +async function JettonWalletCode(): Promise { + const compiledPath = `${PATH_CONTRACTS_JETTON}/JettonWallet.compiled.json` + const compiled = JSON.parse(readFileSync(compiledPath, 'utf8')) + const hex = compiled.hex + if (!hex) { + throw new Error('Compiled JettonWallet code hex not found in JSON') + } + // Remove 0x prefix if present + const hexStr = hex.startsWith('0x') ? hex.slice(2) : hex + const boc = Buffer.from(hexStr, 'hex') + return Cell.fromBoc(boc)[0] +} diff --git a/contracts/tests/pools/LockReleaseTokenPool.setRebalancer.spec.ts b/contracts/tests/pools/LockReleaseTokenPool.setRebalancer.spec.ts new file mode 100644 index 000000000..91995578e --- /dev/null +++ b/contracts/tests/pools/LockReleaseTokenPool.setRebalancer.spec.ts @@ -0,0 +1,158 @@ +import '@ton/test-utils' +import { describe, beforeAll, beforeEach, it, expect } from '@jest/globals' +import { toNano, Address } from '@ton/core' +import { LockReleaseTokenPoolSetup } from './LockReleaseTokenPoolSetup' +import { ZERO_ADDRESS } from '../../src/utils' +import { SandboxContract, TreasuryContract } from '@ton/sandbox' +import { TestCode } from './BaseTest' + +describe('LockReleaseTokenPool - setRebalancer', () => { + let baseTest: LockReleaseTokenPoolSetup + let code: TestCode + + beforeAll(async () => { + code = await LockReleaseTokenPoolSetup.compileContracts() + }) + + beforeEach(async () => { + baseTest = new LockReleaseTokenPoolSetup() + baseTest.code = code + await baseTest.setUp('test-set-rebalancer') + }) + + it('sanity check', async () => { + expect(baseTest).toBeDefined() + }) + + it('should set rebalancer successfully', async () => { + // Test equivalent to test_SetRebalancer() in Solidity + + // Check initial rebalancer (should be OWNER from setUp) + const initialRebalancer = await baseTest.bind.lockReleaseTokenPool.getRebalancer() + expect(initialRebalancer.equals(baseTest.acc.owner.address)).toBe(true) + + // Set new rebalancer to STRANGER + const setRebalancerResult = await baseTest.bind.lockReleaseTokenPool.sendSetRebalancer( + baseTest.acc.owner.getSender(), + toNano('0.05'), + { + queryId: 1n, + rebalancer: baseTest.acc.stranger.address, + }, + ) + + // Verify transaction succeeded + // expect(setRebalancerResult.transactions).toHaveTransaction({ + // from: baseTest.acc.owner.address, + // to: baseTest.bind.lockReleaseTokenPool.address, + // success: true, + // }) + + // TODO: Verify RebalancerSet event was emitted when event parsing is available + // expect(setRebalancerResult.transactions).toHaveTransaction({ + // from: testSetup.acc.owner.address, + // to: testSetup.bind.lockReleaseTokenPool.address, + // success: true, + // outMessagesCount: 1, + // body: expect.objectContaining({ + // type: 'rebalancer-set-event', + // oldRebalancer: testSetup.acc.owner.address, + // newRebalancer: testSetup.acc.stranger.address, + // }), + // }) + + // Verify rebalancer was actually changed + const newRebalancer = await baseTest.bind.lockReleaseTokenPool.getRebalancer() + expect(newRebalancer.equals(baseTest.acc.stranger.address)).toBe(true) + }) + + // it('should revert when non-owner tries to set rebalancer', async () => { + // // Test equivalent to test_SetRebalancer_RevertWhen_OnlyCallableByOwner() in Solidity + + // // Attempt to set rebalancer from non-owner account (STRANGER) + // const setRebalancerResult = await baseTest.bind.lockReleaseTokenPool.sendSetRebalancer( + // baseTest.acc.stranger.getSender(), // Using stranger instead of owner + // toNano('0.05'), + // { + // queryId: 1n, + // rebalancer: baseTest.acc.stranger.address, + // }, + // ) + + // // Verify transaction failed with proper error + // expect(setRebalancerResult.transactions).toHaveTransaction({ + // from: baseTest.acc.stranger.address, + // to: baseTest.bind.lockReleaseTokenPool.address, + // success: false, + // // TODO: Add specific exit code check when Ownable2Step error codes are available + // // exitCode: ownable2step.Error.OnlyCallableByOwner, + // }) + + // // Verify rebalancer was NOT changed (should still be owner) + // const currentRebalancer = await baseTest.bind.lockReleaseTokenPool.getRebalancer() + // expect(currentRebalancer.equals(baseTest.acc.owner.address)).toBe(true) + // }) + + // it('should allow setting rebalancer to zero address (disable rebalancer)', async () => { + // // Additional test case - setting rebalancer to null/zero address should be allowed + // const zeroAddress = ZERO_ADDRESS + + // const setRebalancerResult = await baseTest.bind.lockReleaseTokenPool.sendSetRebalancer( + // baseTest.acc.owner.getSender(), + // toNano('0.05'), + // { + // queryId: 1n, + // rebalancer: zeroAddress, // Setting to zero address to disable + // }, + // ) + + // // Verify transaction succeeded + // expect(setRebalancerResult.transactions).toHaveTransaction({ + // from: baseTest.acc.owner.address, + // to: baseTest.bind.lockReleaseTokenPool.address, + // success: true, + // }) + + // // Verify rebalancer was set to zero address + // const newRebalancer = await baseTest.bind.lockReleaseTokenPool.getRebalancer() + // expect(newRebalancer.equals(zeroAddress)).toBe(true) + // }) + + // it('should emit RebalancerSet event with correct parameters', async () => { + // // Test for proper event emission (when event parsing is available) + // const oldRebalancer = await baseTest.bind.lockReleaseTokenPool.getRebalancer() + // const newRebalancer = baseTest.acc.rebalancer.address + + // const setRebalancerResult = await baseTest.bind.lockReleaseTokenPool.sendSetRebalancer( + // baseTest.acc.owner.getSender(), + // toNano('0.05'), + // { + // queryId: 1n, + // rebalancer: newRebalancer, + // }, + // ) + + // // Verify transaction succeeded + // expect(setRebalancerResult.transactions).toHaveTransaction({ + // from: baseTest.acc.owner.address, + // to: baseTest.bind.lockReleaseTokenPool.address, + // success: true, + // }) + + // // TODO: Add event verification when event parsing infrastructure is available + // // This should verify that a RebalancerSet event was emitted with: + // // - oldRebalancer: oldRebalancer address + // // - newRebalancer: newRebalancer address + // // + // // Example: + // // const events = parseEvents(setRebalancerResult.transactions) + // // const rebalancerSetEvent = events.find(e => e.type === 'RebalancerSet') + // // expect(rebalancerSetEvent).toBeDefined() + // // expect(rebalancerSetEvent.oldRebalancer.equals(oldRebalancer)).toBe(true) + // // expect(rebalancerSetEvent.newRebalancer.equals(newRebalancer)).toBe(true) + + // console.warn( + // 'Event verification not yet implemented - waiting for event parsing infrastructure', + // ) + // }) +}) diff --git a/contracts/tests/pools/LockReleaseTokenPoolSetup.ts b/contracts/tests/pools/LockReleaseTokenPoolSetup.ts new file mode 100644 index 000000000..31c125129 --- /dev/null +++ b/contracts/tests/pools/LockReleaseTokenPoolSetup.ts @@ -0,0 +1,245 @@ +import '@ton/test-utils' + +import { Address, toNano } from '@ton/core' +import { SandboxContract } from '@ton/sandbox' +import { crc32 } from 'zlib' +import { BaseTokenPoolTest } from './BaseTest' +import { lockReleaseTokenPool } from '../../wrappers/pools' +import { ac } from '../../wrappers/lib/access' +import { callproxy } from '../../wrappers/mcms' +import { JettonClientConfig } from '../../wrappers/examples/jetton' +import { ZERO_ADDRESS } from '../../src/utils' + +export type TestContracts = { + ac: SandboxContract + lockReleaseTokenPool: SandboxContract +} + +export class LockReleaseTokenPoolSetup extends BaseTokenPoolTest { + // Test configuration following Solidity setup + static readonly DEFAULT_TOKEN_DECIMALS = 18 + static readonly DEST_CHAIN_SELECTOR = 2n + static readonly SOURCE_CHAIN_SELECTOR = 1n + + // Contract instances + lockReleaseTokenPool?: SandboxContract + lockReleaseTokenPoolWithAllowList?: SandboxContract + + // Addresses following Solidity test pattern + allowedOnRamp!: Address + allowedOffRamp!: Address + destPoolAddress!: Address + sourcePoolAddress!: Address + allowedList: Address[] = [] + + constructor() { + super() + } + + /** + * Setup method that replicates the Solidity setUp function + */ + async setUp(testId: string = 'lock-release-pool-test'): Promise { + // Run base setup first (equivalent to super.setUp()) + await this.setupBasics(testId) + + // Create addresses (equivalent to Solidity address assignments) + this.allowedOnRamp = await this.makeAddr('allowedOnRamp') // address(123) equivalent + this.allowedOffRamp = await this.makeAddr('allowedOffRamp') // address(234) equivalent + this.destPoolAddress = await this.makeAddr('destPoolAddress') // address(2736782345) equivalent + this.sourcePoolAddress = await this.makeAddr('sourcePoolAddress') // address(53852352095) equivalent + + // Deal tokens to owner (equivalent to deal(address(s_token), OWNER, type(uint256).max)) + await this.dealTokens(this.acc.owner.address, 10n ** 27n) // Large amount for testing + + // Setup the main LockReleaseTokenPool + await this.setupLockReleaseTokenPool(testId) + await this.deployLockReleaseTokenPool() + + // Setup allowList and create pool with allowlist + await this.setupAllowList() + await this.setupLockReleaseTokenPoolWithAllowList(testId) + await this.deployLockReleaseTokenPoolWithAllowList() + + // Apply chain updates (equivalent to s_lockReleaseTokenPool.applyChainUpdates) + await this.applyChainUpdates() + + // Set rebalancer (equivalent to s_lockReleaseTokenPool.setRebalancer(OWNER)) + await this.setInitialRebalancer() + + // Setup router ramp updates (equivalent to s_sourceRouter.applyRampUpdates) + await this.setupRouterRamps() + } + + /** + * Setup the main LockReleaseTokenPool contract + */ + async setupLockReleaseTokenPool(testId: string): Promise { + const contractData = lockReleaseTokenPool.builder.data.contractDataEmpty( + crc32(`lock-release-pool-${testId}`), + this.acc.owner.address, + { + masterAddress: this.bind.jettonMinter.address, + jettonWalletCode: this.code.jettonWallet, + }, + LockReleaseTokenPoolSetup.DEFAULT_TOKEN_DECIMALS, + ZERO_ADDRESS, // Mock RMN address + ZERO_ADDRESS, // Mock Router address + ) + + this.bind.lockReleaseTokenPool = this.blockchain.openContract( + lockReleaseTokenPool.ContractClient.newFrom(contractData, this.code.lockReleaseTokenPool), + ) + } + + /** + * Deploy the main LockReleaseTokenPool contract + */ + async deployLockReleaseTokenPool(): Promise { + const deployResult = await this.bind.lockReleaseTokenPool.sendDeploy( + this.acc.deployer.getSender(), + toNano('1'), + ) + + expect(deployResult.transactions).toHaveTransaction({ + from: this.acc.deployer.address, + to: this.bind.lockReleaseTokenPool.address, + deploy: true, + }) + + const topUpResult = await this.bind.lockReleaseTokenPool.sendTopUp( + this.acc.deployer.getSender(), + toNano('1'), + ) + } + + /** + * Setup the allowlist (equivalent to s_allowedList.push operations) + */ + async setupAllowList(): Promise { + this.allowedList = [ + await this.makeAddr('randomAddress'), // vm.randomAddress() equivalent + this.acc.owner.address, + ] + } + + /** + * Setup LockReleaseTokenPool with allowlist + */ + async setupLockReleaseTokenPoolWithAllowList(testId: string): Promise { + const contractData = lockReleaseTokenPool.builder.data.contractDataEmpty( + crc32(`lock-release-pool-allowlist-${testId}`), + this.acc.owner.address, + { + masterAddress: this.bind.jettonMinter.address, + jettonWalletCode: this.code.jettonWallet, + }, + LockReleaseTokenPoolSetup.DEFAULT_TOKEN_DECIMALS, + await this.makeAddr('mockRMN'), + await this.makeAddr('sourceRouter'), + ) + + this.lockReleaseTokenPoolWithAllowList = this.blockchain.openContract( + lockReleaseTokenPool.ContractClient.newFrom(contractData, this.code.lockReleaseTokenPool), + ) + } + + /** + * Deploy the allowlist LockReleaseTokenPool contract + */ + async deployLockReleaseTokenPoolWithAllowList(): Promise { + const deployResult = await this.lockReleaseTokenPoolWithAllowList!.sendTopUp( + this.acc.deployer.getSender(), + toNano('0.05'), + ) + + expect(deployResult.transactions).toHaveTransaction({ + from: this.acc.deployer.address, + to: this.lockReleaseTokenPoolWithAllowList!.address, + deploy: true, + success: true, + }) + } + + /** + * Apply chain updates to both pools (equivalent to applyChainUpdates calls) + */ + async applyChainUpdates(): Promise { + // TODO: Implement chain updates when the wrapper supports it + // This would be equivalent to: + // bytes[] memory remotePoolAddresses = new bytes[](1); + // remotePoolAddresses[0] = abi.encode(s_destPoolAddress); + // TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + // chainUpdate[0] = TokenPool.ChainUpdate({...}); + // s_lockReleaseTokenPool.applyChainUpdates(new uint64[](0), chainUpdate); + + console.warn('Chain updates not yet implemented - waiting for full TokenPool functionality') + } + + /** + * Set initial rebalancer (equivalent to s_lockReleaseTokenPool.setRebalancer(OWNER)) + */ + async setInitialRebalancer(): Promise { + const setRebalancerResult = await this.bind.lockReleaseTokenPool.sendSetRebalancer( + this.acc.owner.getSender(), + toNano('0.05'), + { + queryId: 1n, + rebalancer: this.acc.owner.address, + }, + ) + + expect(setRebalancerResult.transactions).toHaveTransaction({ + from: this.acc.owner.address, + to: this.bind.lockReleaseTokenPool.address, + success: true, + }) + } + + /** + * Setup router ramp updates (equivalent to s_sourceRouter.applyRampUpdates) + */ + async setupRouterRamps(): Promise { + // TODO: Implement router ramp updates when Router wrapper is available + // This would be equivalent to: + // Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + // Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + // onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_allowedOnRamp}); + // offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: s_allowedOffRamp}); + // s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + + console.warn('Router ramp updates not yet implemented - waiting for Router wrapper') + } + + /** + * Get the allowlist LockReleaseTokenPool contract instance + */ + getLockReleaseTokenPoolWithAllowList(): SandboxContract { + if (!this.lockReleaseTokenPoolWithAllowList) { + throw new Error('LockReleaseTokenPoolWithAllowList not initialized. Call setUp() first.') + } + return this.lockReleaseTokenPoolWithAllowList + } + + /** + * Get the outbound rate limiter config (helper from base class) + */ + getOutboundRateLimiterConfig(): any { + return { + isEnabled: true, + capacity: 100n * 10n ** 28n, // 100e28 + rate: 10n ** 15n, // 1e15 + } + } + + /** + * Get the inbound rate limiter config (helper from base class) + */ + getInboundRateLimiterConfig(): any { + return { + isEnabled: true, + capacity: 222n * 10n ** 30n, // 222e30 + rate: 10n ** 18n, // 1e18 + } + } +} diff --git a/contracts/wrappers/pools.LockReleaseTokenPool.compile.ts b/contracts/wrappers/pools.LockReleaseTokenPool.compile.ts new file mode 100644 index 000000000..727bba111 --- /dev/null +++ b/contracts/wrappers/pools.LockReleaseTokenPool.compile.ts @@ -0,0 +1,8 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/pools/lock_release_token_pool.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + experimentalOptions: '', // you can pass experimental compiler options here +} diff --git a/contracts/wrappers/pools/LockReleaseTokenPool.ts b/contracts/wrappers/pools/LockReleaseTokenPool.ts new file mode 100644 index 000000000..83066d70f --- /dev/null +++ b/contracts/wrappers/pools/LockReleaseTokenPool.ts @@ -0,0 +1,606 @@ +import { + Address, + beginCell, + Builder, + Cell, + Contract, + contractAddress, + ContractProvider, + Dictionary, + Sender, + SendMode, + Slice, +} from '@ton/core' +import { crc32 } from 'zlib' +import { CellCodec } from '../utils' +import { asSnakeData, fromSnakeData, ZERO_ADDRESS } from '../../src/utils' +import * as ownable2step from '../libraries/access/Ownable2Step' +import { CrossChainAddress } from '../ccip/OffRamp' +import * as jetton from '../examples/jetton' +import { loadDict, loadMap } from '../../src/utils/dict' +import { Data, RemoteChainConfig } from './TokenPools' +import { tokenPool } from '.' + +// --- Message types following the .tolk patterns --- + +// @dev Gets rebalancer address +export type GetRebalancer = { + queryId: bigint +} + +// @dev Sets the rebalancer address. +export type SetRebalancer = { + queryId: bigint + rebalancer: Address +} + +// @dev Adds liquidity to the pool. +export type ProvideLiquidity = { + queryId: bigint + amount: bigint +} + +// @dev Removes liquidity from the pool. +export type WithdrawLiquidity = { + queryId: bigint + amount: bigint +} + +// @dev Transfer liquidity from an older version of the pool. +export type TransferLiquidity = { + queryId: bigint + from: Address + amount: bigint +} + +// @dev Get the router address +export type GetRouter = { + queryId: bigint +} + +// @dev Set the router address +export type SetRouter = { + queryId: bigint + newRouter: Address +} + +// @dev Check if token is supported +export type IsSupportedToken = { + queryId: bigint + token: Address +} + +// @dev Get the token managed by this pool +export type GetToken = { + queryId: bigint +} + +// @dev Get RMN proxy address +export type GetRmnProxy = { + queryId: bigint +} + +// @dev Lock or burn tokens - simplified for now +export type LockOrBurn = { + queryId: bigint + // We'll add the full Pool_LockOrBurnInV1 structure when needed + receiver: Cell // encoded receiver address + remoteChainSelector: bigint + originalSender: Address + amount: bigint + localToken: Address +} + +// @dev Release or mint tokens - simplified for now +export type ReleaseOrMint = { + queryId: bigint + // We'll add the full Pool_ReleaseOrMintInV1 structure when needed + originalSender: Cell // encoded original sender + remoteChainSelector: bigint + receiver: Address + sourceDenominatedAmount: bigint + localToken: Address +} + +// @dev Union of all input messages +export type InMessage = + | GetRebalancer + | SetRebalancer + | ProvideLiquidity + | WithdrawLiquidity + | TransferLiquidity + | GetRouter + | SetRouter + | IsSupportedToken + | GetToken + | GetRmnProxy + | LockOrBurn + | ReleaseOrMint + +// --- Response types --- + +export type GetRebalancerResponse = { + queryId: bigint + rebalancer: Address +} + +export type GetRouterResponse = { + queryId: bigint + router: Address +} + +export type GetTokenResponse = { + queryId: bigint + token: Address +} + +export type GetRmnProxyResponse = { + queryId: bigint + rmnProxy: Address +} + +export type IsSupportedTokenResponse = { + queryId: bigint + isSupported: boolean +} + +// Contract storage data +export type ContractData = { + /// ID allows multiple independent instances + id: number // uint32 + + /// Ownable trait data + ownable: ownable2step.Data + + /// The rebalancer address + rebalancer: Address | null + + /// TokenPoolData + tokenPoolData: Data +} + +// --- Error codes from .tolk files --- +export enum Error { + INSUFFICIENT_LIQUIDITY = 1001, + // TokenPool errors + CALLER_IS_NOT_A_RAMP_ON_ROUTER = 1001, + ZERO_ADDRESS_INVALID = 1002, + SENDER_NOT_ALLOWED = 1003, + ALLOW_LIST_NOT_ENABLED = 1004, + NON_EXISTENT_CHAIN = 1005, + CHAIN_NOT_ALLOWED = 1006, + CURSED_BY_RMN = 1007, + CHAIN_ALREADY_EXISTS = 1008, + INVALID_SOURCE_POOL_ADDRESS = 1009, + INVALID_TOKEN = 1010, + UNAUTHORIZED = 1011, + POOL_ALREADY_ADDED = 1012, + INVALID_REMOTE_POOL_FOR_CHAIN = 1013, + INVALID_REMOTE_CHAIN_DECIMALS = 1014, + MISMATCHED_ARRAY_LENGTHS = 1015, + OVERFLOW_DETECTED = 1016, + INVALID_DECIMAL_ARGS = 1017, +} + +// --- Opcodes following .tolk patterns --- +export const opcodes = { + in: { + // Basic messages + TopUp: 0x00000000, // TODO: Define proper opcode when needed + + // LockReleaseTokenPool specific + GetRebalancer: 0x1a2b3c4d, + SetRebalancer: 0x2b3c4d5e, + ProvideLiquidity: 0x3c4d5e6f, + WithdrawLiquidity: 0x4d5e6f70, + TransferLiquidity: 0x5e6f7081, + + // TokenPool base + IsSupportedToken: 0x11223344, + GetToken: 0x22334455, + GetRmnProxy: 0x33445566, + GetRouter: 0x44556677, + SetRouter: 0x55667788, + LockOrBurn: 0x7788990a, + ReleaseOrMint: 0x8899aa0b, + }, + out: { + // Response opcodes + GetRebalancerResponse: 0xa1b2c3d4, + SetRebalancerResponse: 0xb2c3d4e5, + ProvideLiquidityResponse: 0xc3d4e5f6, + WithdrawLiquidityResponse: 0xd4e5f607, + TransferLiquidityResponse: 0xe5f60718, + + // Events (using crc32 for consistency) + RebalancerSet: crc32('LockReleaseTokenPool_RebalancerSet'), + LiquidityAdded: crc32('LockReleaseTokenPool_LiquidityAdded'), + LiquidityRemoved: crc32('LockReleaseTokenPool_LiquidityRemoved'), + LiquidityTransferred: crc32('LockReleaseTokenPool_LiquidityTransferred'), + RouterUpdated: crc32('TokenPool_RouterUpdated'), + }, +} + +// --- Message builders --- +export const builder = { + message: { + in: { + topUp: { + encode: (): Builder => { + return new Builder() + }, + load: (src: Slice): null => { + src.skip(32) // skip opcode + return null + }, + }, + + getRebalancer: { + encode: (msg: GetRebalancer): Builder => { + return beginCell().storeUint(opcodes.in.GetRebalancer, 32).storeUint(msg.queryId, 64) + }, + load: (src: Slice): GetRebalancer => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + } + }, + }, + + setRebalancer: { + encode: (msg: SetRebalancer): Builder => { + return beginCell() + .storeUint(opcodes.in.SetRebalancer, 32) + .storeUint(msg.queryId, 64) + .storeAddress(msg.rebalancer) + }, + load: (src: Slice): SetRebalancer => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + rebalancer: src.loadAddress(), + } + }, + }, + + provideLiquidity: { + encode: (msg: ProvideLiquidity): Builder => { + return beginCell() + .storeUint(opcodes.in.ProvideLiquidity, 32) + .storeUint(msg.queryId, 64) + .storeUint(msg.amount, 256) + }, + load: (src: Slice): ProvideLiquidity => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + amount: src.loadUintBig(256), + } + }, + }, + + withdrawLiquidity: { + encode: (msg: WithdrawLiquidity): Builder => { + return beginCell() + .storeUint(opcodes.in.WithdrawLiquidity, 32) + .storeUint(msg.queryId, 64) + .storeUint(msg.amount, 256) + }, + load: (src: Slice): WithdrawLiquidity => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + amount: src.loadUintBig(256), + } + }, + }, + + transferLiquidity: { + encode: (msg: TransferLiquidity): Builder => { + return beginCell() + .storeUint(opcodes.in.TransferLiquidity, 32) + .storeUint(msg.queryId, 64) + .storeAddress(msg.from) + .storeUint(msg.amount, 256) + }, + load: (src: Slice): TransferLiquidity => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + from: src.loadAddress(), + amount: src.loadUintBig(256), + } + }, + }, + + getRouter: { + encode: (msg: GetRouter): Builder => { + return beginCell().storeUint(opcodes.in.GetRouter, 32).storeUint(msg.queryId, 64) + }, + load: (src: Slice): GetRouter => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + } + }, + }, + + setRouter: { + encode: (msg: SetRouter): Builder => { + return beginCell() + .storeUint(opcodes.in.SetRouter, 32) + .storeUint(msg.queryId, 64) + .storeAddress(msg.newRouter) + }, + load: (src: Slice): SetRouter => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + newRouter: src.loadAddress(), + } + }, + }, + + isSupportedToken: { + encode: (msg: IsSupportedToken): Builder => { + return beginCell() + .storeUint(opcodes.in.IsSupportedToken, 32) + .storeUint(msg.queryId, 64) + .storeAddress(msg.token) + }, + load: (src: Slice): IsSupportedToken => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + token: src.loadAddress(), + } + }, + }, + + getToken: { + encode: (msg: GetToken): Builder => { + return beginCell().storeUint(opcodes.in.GetToken, 32).storeUint(msg.queryId, 64) + }, + load: (src: Slice): GetToken => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + } + }, + }, + + getRmnProxy: { + encode: (msg: GetRmnProxy): Builder => { + return beginCell().storeUint(opcodes.in.GetRmnProxy, 32).storeUint(msg.queryId, 64) + }, + load: (src: Slice): GetRmnProxy => { + src.skip(32) // skip opcode + return { + queryId: src.loadUintBig(64), + } + }, + }, + }, + }, + + data: (() => { + // Contract data codec + const contractData: CellCodec = { + encode: (data: ContractData): Builder => { + const tokenPoolData = tokenPool.builder.data.contractData.encode(data.tokenPoolData) + // Simplified encoding - extend as needed + return beginCell() + .storeUint(data.id, 32) + .storeAddress(data.rebalancer) + .storeBuilder(ownable2step.builder.data.traitData.encode(data.ownable)) + .storeRef(tokenPoolData) + }, + load: (src: Slice): ContractData => { + const id = src.loadUint(32) + const ownable = ownable2step.builder.data.traitData.load(src) + const rebalancer = src.loadMaybeAddress() + const tokenPoolData = tokenPool.builder.data.contractData.load(src.loadRef().beginParse()) + return { + id, + ownable, + rebalancer, + tokenPoolData, + } + }, + } + + const contractDataEmpty = ( + id: number, + owner: Address, + token: jetton.JettonClientConfig, + tokenDecimals: number, + rmnProxy: Address, + router: Address, + ): ContractData => { + return { + id, + ownable: { + owner, + pendingOwner: null, + }, + rebalancer: null, + tokenPoolData: { + token, + tokenDecimals, + router, + allowListEnabled: false, + allowList: new Set
(), + remoteChainSelectors: new Map(), + remoteChainConfigs: new Map(), + remotePoolAddresses: new Map(), + rateLimitAdmin: ZERO_ADDRESS, + }, + } + } + + return { + contractData, + contractDataEmpty, + } + })(), +} + +// --- Contract client --- +export class ContractClient implements Contract { + constructor( + readonly address: Address, + readonly init?: { code: Cell; data: Cell }, + ) {} + + static newAt(address: Address): ContractClient { + return new ContractClient(address) + } + + static newFrom(data: ContractData, code: Cell, workchain = 0): ContractClient { + const init = { code, data: builder.data.contractData.encode(data).asCell() } + return new ContractClient(contractAddress(workchain, init), init) + } + + async sendInternal(p: ContractProvider, via: Sender, value: bigint, body: Cell) { + await p.internal(via, { + value: value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: body, + }) + } + + // --- Send methods --- + async sendDeploy(p: ContractProvider, via: Sender, value: bigint) { + return this.sendInternal(p, via, value, builder.message.in.topUp.encode().asCell()) + } + + async sendTopUp(p: ContractProvider, via: Sender, value: bigint) { + return this.sendInternal(p, via, value, builder.message.in.topUp.encode().asCell()) + } + + async sendGetRebalancer(p: ContractProvider, via: Sender, value: bigint, body: GetRebalancer) { + return this.sendInternal(p, via, value, builder.message.in.getRebalancer.encode(body).asCell()) + } + + async sendSetRebalancer(p: ContractProvider, via: Sender, value: bigint, body: SetRebalancer) { + return this.sendInternal(p, via, value, builder.message.in.setRebalancer.encode(body).asCell()) + } + + async sendProvideLiquidity( + p: ContractProvider, + via: Sender, + value: bigint, + body: ProvideLiquidity, + ) { + return this.sendInternal( + p, + via, + value, + builder.message.in.provideLiquidity.encode(body).asCell(), + ) + } + + async sendWithdrawLiquidity( + p: ContractProvider, + via: Sender, + value: bigint, + body: WithdrawLiquidity, + ) { + return this.sendInternal( + p, + via, + value, + builder.message.in.withdrawLiquidity.encode(body).asCell(), + ) + } + + async sendTransferLiquidity( + p: ContractProvider, + via: Sender, + value: bigint, + body: TransferLiquidity, + ) { + return this.sendInternal( + p, + via, + value, + builder.message.in.transferLiquidity.encode(body).asCell(), + ) + } + + async sendGetRouter(p: ContractProvider, via: Sender, value: bigint, body: GetRouter) { + return this.sendInternal(p, via, value, builder.message.in.getRouter.encode(body).asCell()) + } + + async sendSetRouter(p: ContractProvider, via: Sender, value: bigint, body: SetRouter) { + return this.sendInternal(p, via, value, builder.message.in.setRouter.encode(body).asCell()) + } + + async sendIsSupportedToken( + p: ContractProvider, + via: Sender, + value: bigint, + body: IsSupportedToken, + ) { + return this.sendInternal( + p, + via, + value, + builder.message.in.isSupportedToken.encode(body).asCell(), + ) + } + + async sendGetToken(p: ContractProvider, via: Sender, value: bigint, body: GetToken) { + return this.sendInternal(p, via, value, builder.message.in.getToken.encode(body).asCell()) + } + + async sendGetRmnProxy(p: ContractProvider, via: Sender, value: bigint, body: GetRmnProxy) { + return this.sendInternal(p, via, value, builder.message.in.getRmnProxy.encode(body).asCell()) + } + + // --- Getter methods --- + async getTypeAndVersion(p: ContractProvider): Promise<[string, string]> { + const r = await p.get('typeAndVersion', []) + const type = r.stack.readString() + const version = r.stack.readString() + return [type, version] + } + + async getRebalancer(p: ContractProvider): Promise
{ + const r = await p.get('getRebalancer', []) + return r.stack.readAddress() + } + + async getRouter(p: ContractProvider): Promise
{ + const r = await p.get('getRouter', []) + return r.stack.readAddress() + } + + async getToken(p: ContractProvider): Promise
{ + const r = await p.get('getToken', []) + return r.stack.readAddress() + } + + async getRmnProxy(p: ContractProvider): Promise
{ + const r = await p.get('getRmnProxy', []) + return r.stack.readAddress() + } + + async isSupportedToken(p: ContractProvider, token: Address): Promise { + const r = await p.get('isSupportedToken', [ + { type: 'slice', cell: beginCell().storeAddress(token).endCell() }, + ]) + return r.stack.readBoolean() + } + + async getTokenDecimals(p: ContractProvider): Promise { + const r = await p.get('getTokenDecimals', []) + return r.stack.readNumber() + } + + // TODO: Add more getters as needed + // async getCurrentInboundRateLimiterState(p: ContractProvider): Promise + // async getCurrentOutboundRateLimiterState(p: ContractProvider): Promise + // async getRemotePools(p: ContractProvider, remoteChainSelector: bigint): Promise + // async isRemotePool(p: ContractProvider, remoteChainSelector: bigint, remotePoolAddress: Cell): Promise +} diff --git a/contracts/wrappers/pools/RateLimiter.ts b/contracts/wrappers/pools/RateLimiter.ts new file mode 100644 index 000000000..5603e39ae --- /dev/null +++ b/contracts/wrappers/pools/RateLimiter.ts @@ -0,0 +1,82 @@ +import { beginCell, Builder, Cell, Slice } from '@ton/core' +import { CellCodec } from '../utils' + +export type TokenBucket = { + tokens: bigint // ─────╮ Current number of tokens that are in the bucket. + lastUpdated: bigint // │ Timestamp in seconds of the last token refill, good for 100+ years. + isEnabled: boolean // ─╯ Indication whether the rate limiting is enabled or not. + capacity: bigint // ──╮ Maximum number of tokens that can be in the bucket. + rate: bigint // ──────╯ Number of tokens per second that the bucket is refilled. +} + +export type Config = { + isEnabled: boolean // Indication whether the rate limiting should be enabled. + capacity: bigint // ──╮ Specifies the capacity of the rate limiter. + rate: bigint // ──────╯ Specifies the rate of the rate limiter. +} + +export type Data = { + bucket: TokenBucket +} + +const loadTokenBucket = (slice: Slice): TokenBucket => { + const tokens = slice.loadUintBig(128) + const lastUpdated = slice.loadUintBig(32) + const isEnabled = slice.loadBit() + const capacity = slice.loadUintBig(128) + const rate = slice.loadUintBig(128) + return { + tokens, + lastUpdated, + isEnabled, + capacity, + rate, + } +} + +const loadConfig = (slice: Slice): Config => { + return { + isEnabled: slice.loadBit(), + capacity: slice.loadUintBig(128), + rate: slice.loadUintBig(128), + } +} + +export const builder = { + data: (() => { + const contractData: CellCodec = { + encode: (data: Data): Builder => { + return beginCell().storeBuilder(tokenBucket.encode(data.bucket)) + }, + load: (src: Slice): Data => { + return { + bucket: tokenBucket.load(src), + } + }, + } + const config: CellCodec = { + encode: (data: Config): Builder => { + return beginCell() + .storeBit(data.isEnabled) + .storeUint(data.capacity, 128) + .storeUint(data.rate, 128) + }, + load: loadConfig, + } + const tokenBucket: CellCodec = { + encode: (data: TokenBucket): Builder => { + return beginCell() + .storeUint(data.tokens, 128) + .storeUint(data.lastUpdated, 256) + .storeBit(data.isEnabled) + .storeUint(data.capacity, 128) + .storeUint(data.rate, 128) + }, + load: loadTokenBucket, + } + return { + tokenBucket, + config, + } + })(), +} diff --git a/contracts/wrappers/pools/TokenPools.ts b/contracts/wrappers/pools/TokenPools.ts new file mode 100644 index 000000000..849dfce48 --- /dev/null +++ b/contracts/wrappers/pools/TokenPools.ts @@ -0,0 +1,190 @@ +import { CrossChainAddress } from '../ccip/OffRamp' +import { TokenBucket } from './RateLimiter' +import * as ownable2step from '../libraries/access/Ownable2Step' +import * as jetton from '../examples/jetton' +import { Address, beginCell, Builder, Cell, Dictionary, Slice } from '@ton/core' +import { loadDict, loadMap } from '../../src/utils/dict' +import { asSnakeData, fromSnakeData } from '../../src/utils' +import { rateLimiter } from '.' +import { CellCodec } from '../utils' + +export type RemoteChainConfig = { + outboundRateLimiterConfig: TokenBucket // Outbound rate limited config, meaning the rate limits for all of the onRamps for the given chain + inboundRateLimiterConfig: TokenBucket // Inbound rate limited config, meaning the rate limits for all of the offRamps for the given chain + remoteTokenAddress: CrossChainAddress // address of the remote token, ABI encoded in the case of a remote EVM chain. + remotePools: bigint[] // vec<256> // Set of remote pool hashes, ABI encoded in the case of a remote EVM chain. // Original implementation uses bytes32 but in tolk keccak returns 256 +} + +export type Data = { + /// The token managed by this pool + token: jetton.JettonClientConfig + + /// Token decimals + tokenDecimals: number // uint8 + + /// RMN proxy address + // rmnProxy: Address + + allowListEnabled: boolean + + allowList: Set
+ + /// Router address + router: Address + + remoteChainSelectors: Map // uint64 -> uint64 + + remoteChainConfigs: Map // uint64 -> cell + + remotePoolAddresses: Map + + rateLimitAdmin: Address +} + +export const builder = { + data: (() => { + const contractData: CellCodec = { + encode: (tokenPoolData: Data): Builder => { + const allowListMap = new Map() + for (const [k, v] of tokenPoolData.allowList.entries()) { + allowListMap.set(k, true) + } + + const remoteChainConfigs = new Map() + for (const [k, v] of tokenPoolData.remoteChainConfigs.entries()) { + remoteChainConfigs.set(k, builder.data.remoteChainConfig.encode(v).asCell()) + } + + const remotePoolAddresses = new Map() + for (const [k, v] of tokenPoolData.remotePoolAddresses.entries()) { + remotePoolAddresses.set( + k, + beginCell() + .storeBuffer(v as Buffer) + .endCell(), + ) + } + + return beginCell() + .storeRef(jetton.builder.data.traitData.encode(tokenPoolData.token).asCell()) + .storeUint(tokenPoolData.tokenDecimals, 8) + .storeBit(tokenPoolData.allowListEnabled) + .storeDict(loadMap(Dictionary.Keys.Address(), Dictionary.Values.Bool(), allowListMap)) + .storeAddress(tokenPoolData.router) + .storeDict( + loadMap( + Dictionary.Keys.Uint(64), + Dictionary.Values.Uint(64), + tokenPoolData.remoteChainSelectors, + ), + ) + .storeDict( + loadMap(Dictionary.Keys.Uint(64), Dictionary.Values.Cell(), remoteChainConfigs), + ) + .storeDict( + loadMap(Dictionary.Keys.BigUint(256), Dictionary.Values.Cell(), remotePoolAddresses), + ) + .storeAddress(tokenPoolData.rateLimitAdmin) + }, + + load: (src: Slice): Data => { + const token = jetton.builder.data.traitData.load(src.loadRef().beginParse()) + const tokenDecimals = src.loadUint(8) + const allowListEnabled = src.loadBit() + const allowListMap = loadDict( + src.loadDict(Dictionary.Keys.Address(), Dictionary.Values.Bool()), + ) + const allowList = new Set
() + for (const [k, v] of allowListMap.entries()) { + allowList.add(k) + } + const router = src.loadAddress() + const remoteChainSelectors = loadDict( + src.loadDict(Dictionary.Keys.Uint(64), Dictionary.Values.Uint(64)), + ) + const remoteChainConfigsCells = loadDict( + src.loadDict(Dictionary.Keys.Uint(64), Dictionary.Values.Cell()), + ) + const remoteChainConfigs = new Map() + for (const [k, v] of remoteChainConfigsCells.entries()) { + remoteChainConfigs.set(k, builder.data.remoteChainConfig.load(v.beginParse())) + } + const remotePoolAddressesAsCells = loadDict( + src.loadDict(Dictionary.Keys.BigUint(256), Dictionary.Values.Cell()), + ) + const remotePoolAddresses = new Map() + for (const [k, v] of remotePoolAddressesAsCells.entries()) { + const vSlice = v.beginParse() + remotePoolAddresses.set( + k, + vSlice.loadBuffer(vSlice.remainingBits / 8) as CrossChainAddress, + ) + } + const rateLimitAdmin = src.loadAddress() + return { + token, + tokenDecimals, + allowListEnabled, + allowList, + router, + remoteChainSelectors, + remoteChainConfigs, + remotePoolAddresses, + rateLimitAdmin, + } + }, + } + const remoteChainConfig: CellCodec = { + encode: (config: RemoteChainConfig): Builder => { + const remotePools = new Map() + for (const v of config.remotePools) { + remotePools.set(v, v) + } + + return beginCell() + .storeBuilder( + rateLimiter.builder.data.tokenBucket.encode(config.outboundRateLimiterConfig), + ) + .storeBuilder( + rateLimiter.builder.data.tokenBucket.encode(config.inboundRateLimiterConfig), + ) + .storeRef( + beginCell() + .storeBuffer(config.remoteTokenAddress as Buffer) + .endCell(), + ) + .storeDict( + loadMap(Dictionary.Keys.BigUint(256), Dictionary.Values.BigUint(256), remotePools), + ) + }, + + load: (src: Slice): RemoteChainConfig => { + const outboundRateLimiterConfig = rateLimiter.builder.data.tokenBucket.load(src) + const inboundRateLimiterConfig = rateLimiter.builder.data.tokenBucket.load(src) + const remoteTokenAddressSlice = src.loadRef().beginParse() + const remoteTokenAddress = remoteTokenAddressSlice.loadBuffer( + remoteTokenAddressSlice.remainingBits / 8, + ) + + const remotePoolsDict = loadDict( + src.loadDict(Dictionary.Keys.BigUint(256), Dictionary.Values.BigUint(256)), + ) + const remotePools: bigint[] = [] + for (const [k, _v] of remotePoolsDict.entries()) { + remotePools.push(k) + } + return { + outboundRateLimiterConfig, + inboundRateLimiterConfig, + remoteTokenAddress, + remotePools, + } + }, + } + + return { + contractData, + remoteChainConfig, + } + })(), +} diff --git a/contracts/wrappers/pools/index.ts b/contracts/wrappers/pools/index.ts new file mode 100644 index 000000000..9147cb997 --- /dev/null +++ b/contracts/wrappers/pools/index.ts @@ -0,0 +1,3 @@ +export * as lockReleaseTokenPool from './LockReleaseTokenPool' +export * as tokenPool from './TokenPools' +export * as rateLimiter from './RateLimiter'