Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions contracts/Acton.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ src = "contracts/mcms/mcms.tolk"
domain = "mcms"
depends = []

[contracts.Test_RemainingBitsOrRef]
display-name = "link.chain.ton.test.lib.RemainingBitsOrRef"
src = "contracts/test/lib/remaining_bits_or_ref.tolk"
domain = "test"
depends = []

# fmt is not very mature yet
[fmt]
width = 100
Expand Down
1 change: 1 addition & 0 deletions contracts/contracts/ccip/onramp/messages.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import "types"
import "../router/messages"
import "../../lib/utils"
import "../../lib/remaining_bits_or_ref"
import "../../lib/versioning/upgradeable"
import "../ccipsend_executor/messages"
import "../fee_quoter/messages"
Expand Down
1 change: 1 addition & 0 deletions contracts/contracts/ccip/router/messages.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import "types"
import "../common/types.tolk"
import "../../lib/utils"
import "../../lib/remaining_bits_or_ref"
import "../../lib/funding/withdrawable.tolk";
import "../../lib/versioning/upgradeable"
import "../../lib/receiver/types"
Expand Down
33 changes: 33 additions & 0 deletions contracts/contracts/lib/remaining_bits_or_ref.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// This type works as a wrapper for RemainingBitsAndRefs that implements
// automatic serialization/deserialization. It determines whether to store the
// data directly as bits or as a reference to a cell, depending on available
// space.
type RemainingBitsOrRef<T> = T;

fun RemainingBitsOrRef<T>.packToBuilder(self, mutate b: builder): void {
val contentCell = (self as T).toCell();
val (contentBits, contentRefs) = contentCell.beginParse().remainingBitsAndRefsCount();
val availableBits = 1023 - b.bitsCount();
val availableRefs = 4 - b.refsCount();
if (availableBits < contentBits + 1 || availableRefs < contentRefs) {
// not enough space, create a new cell
b.storeBool(true);
b.storeRef(contentCell);
} else {
// can be inlined
b.storeBool(false);
b.storeSlice(contentCell.beginParse());
}
}

fun RemainingBitsOrRef<T>.unpackFromSlice(mutate s: slice): RemainingBitsOrRef<T> {
val isRef = s.loadBool();
var result: RemainingBitsOrRef<T>;
if (isRef) {
val c = s.loadRef();
result = T.fromCell(c) as RemainingBitsOrRef<T>;
} else {
result = T.fromSlice(s) as RemainingBitsOrRef<T>;
}
return result;
}
31 changes: 0 additions & 31 deletions contracts/contracts/lib/utils.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -239,34 +239,3 @@ fun keysLispList<K, V>(dict: map<K, V>): lisp_list<K> {
} ;
return lisplist;
}

// This type works as a wrapper for RemainingBitsAndRefs that implements
// automatic serialization/deserialization. It determines whether to store the
// data directly as bits or as a reference to a cell, depending on available
// space.
type RemainingBitsOrRef<T> = T;

// TODO investigate why this was being ignored in 1.2

// fun RemainingBitsOrRef<T>.packToBuilder(self, mutate b: builder): void {
// val serialized = (self as T).toCell();
// val (nBits, _) = serialized.beginParse().remainingBitsAndRefsCount();
// if (b.remainingSpace() < nBits + 1) {
// // not enough space, create a new cell
// b.storeBool(true);
// b.storeRef(serialized);
// } else {
// b.storeBool(false);
// b.storeSlice(serialized.beginParse());
// }
// }

// fun RemainingBitsOrRef<T>.unpackFromSlice(mutate s: slice): RemainingBitsOrRef<T> {
// val isRef = s.loadBool();
// if (isRef) {
// val c = s.loadRef();
// return T.fromCell(c) as RemainingBitsOrRef<T>;
// } else {
// return T.fromSlice(s) as RemainingBitsOrRef<T>;
// }
// }
113 changes: 113 additions & 0 deletions contracts/contracts/test/lib/remaining_bits_or_ref.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: MIT
tolk 1.4.1

import "../../lib/remaining_bits_or_ref"


contract RemainingBitsOrRefTestWrapper {
author: "SmartContract Chainlink Limited SEZC"
version: "1.6.1"
description: "Test wrapper for RemainingBitsOrRef library"
}

fun onInternalMessage(in: InMessage) {
assert (in.body.isEmpty()) throw 0xFFFF
}

const MAX_CELLS = 4;

struct IncompleteCell {
_1: uint256 = 1
}

// 1 bit is used to indicate whether the data is stored inline or as a reference
struct FullCell {
_1: uint256 = 1
_2: uint256 = 2
_3: uint256 = 3
_4: RemainingBitsOrRef<uint254> = 4
}

struct JustOverFullCell {
_1: uint256 = 1
_2: uint256 = 2
_3: uint256 = 3
_4: RemainingBitsOrRef<uint255> = 4
}

struct _WithReference {
_12: uint254 = 12
_cell: Cell<uint256>
}

fun New_WithReference(): _WithReference {
return _WithReference { _cell: (34 as uint256).toCell() };
}

struct InlinedWithReference {
_1: uint256 = 1
_2: uint256 = 2
_3: uint256 = 3
_4: RemainingBitsOrRef<_WithReference>
}

fun NewInlinedWithReference(): InlinedWithReference {
return InlinedWithReference { _4: New_WithReference() };
}

struct _TooManyReferences {
_1: Cell<uint256>
_2: Cell<uint256>
_3: Cell<uint256>
_4: Cell<uint256>
}

fun _newTooManyReferences(): _TooManyReferences {
return _TooManyReferences {
_1: (11 as uint256).toCell(),
_2: (12 as uint256).toCell(),
_3: (13 as uint256).toCell(),
_4: (14 as uint256).toCell(),
}
}

struct TooManyReferencesToInline {
_1: Cell<uint256>
_2: Cell<uint256>
_3: Cell<uint256>
_4: RemainingBitsOrRef<_TooManyReferences>
}

fun NewTooManyReferencesToInline(): TooManyReferencesToInline {
return TooManyReferencesToInline {
_1: (1 as uint256).toCell(),
_2: (2 as uint256).toCell(),
_3: (3 as uint256).toCell(),
_4: _newTooManyReferences()
}
}

/// expected to fit in 256 bits of a single cell
get fun test_incomplete_cell(): cell {
return RemainingBitsOrRef<IncompleteCell> { }.toCell();
}

/// expected to fit in 1023 bits of a single cell
get fun test_full_cell(): cell {
return RemainingBitsOrRef<FullCell> { }.toCell();
}

/// expected to require 1024 bits (1 more than full cell), so the 4th field should be stored as a reference
get fun test_just_over_full_cell(): cell {
return JustOverFullCell { }.toCell();
}

/// expected to fit in 256 bits of a single cell, and the reference come from the root cell
get fun test_inlined_with_reference(): cell {
return NewInlinedWithReference().toCell();
}

/// expected to be stored as a reference, because inlining would require 5 references which is more than the maximum of 4 allowed
get fun test_too_many_references_to_inline(): cell {
return NewTooManyReferencesToInline().toCell();
}
24 changes: 20 additions & 4 deletions contracts/tests/ccip/onramp/OnRamp.getFee.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ describe('OnRamp - Get Fee', () => {
})

it('should forward get fee to fee quoter', async () => {
const requestCtx = beginCell().storeUint(42, 32).asSlice() // arbitrary context
const result = await onramp.sendGetValidatedFee(mockRouter.getSender(), {
value: toNano('0.5'),
msg: ccipSend,
context: beginCell().storeUint(42, 32).endCell(), // arbitrary context
context: requestCtx,
})

expect(result.transactions).toHaveTransaction({
Expand Down Expand Up @@ -105,9 +106,24 @@ describe('OnRamp - Get Fee', () => {
if (outMsg.info.type !== 'internal') {
throw new Error('Unexpected message type')
}
expect(outMsg.body.beginParse().loadUint(32)).toBe(fq.opcodes.in.getValidatedFee)
const decoded = fq.builder.message.in.getValidatedFee.load(outMsg.body.beginParse())
expect(decoded.msg).toEqual(ccipSend)
const outMsgBody = outMsg.body.beginParse()
expect(outMsgBody.preloadUint(32)).toBe(fq.opcodes.in.getValidatedFee)

// The OnRamp wraps the user context with the router address (OnRamp_GetValidatedFeeContext)
const expectedContext = or.builder.data.getValidatedFeeContext
.encode({
onrampContext: mockRouter.address,
userContext: beginCell().storeUint(42, 32).asSlice(),
})
.asSlice()
const expectedOutMsgBody = fq.builder.message.in.getValidatedFee
.encode({
msg: ccipSend,
context: expectedContext,
})
.asCell()

expect(outMsg.body.toString()).toEqual(expectedOutMsgBody.toString())
})

it('should throw error if message validated comes from non-feequoter', async () => {
Expand Down
7 changes: 7 additions & 0 deletions contracts/tests/ccip/router/Router.getFee.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox'
import { asSnakeDataUint, fromSnakeData, WRAPPED_NATIVE } from '../../../src/utils'
import * as coverage from '../../coverage/coverage'

import * as remainingBitsOrRef from '../../../wrappers/libraries/utils/RemainingBitsOrRef'
import * as rtOld from '../../../wrappers/ccip/Router'
import * as rt from '../../../wrappers/gen/ccip/Router'
import * as or from '../../../wrappers/ccip/OnRamp'
Expand Down Expand Up @@ -55,6 +56,12 @@ describe('Router', () => {
CrossChainAddress__packToBuilder,
CrossChainAddress__unpackFromSlice,
)

rt.Router.registerCustomPackUnpack(
'RemainingBitsOrRef',
remainingBitsOrRef.builder.encode,
remainingBitsOrRef.builder.load,
)
})

beforeEach(async () => {
Expand Down
Loading
Loading