Skip to content

Commit e6a9d88

Browse files
archseerhuangzhen1997jadepark-devvicentevieytesnicolasgnr
authored
Support TON as destination (#140)
* add msg, need to test * add unit tests * goimport * add typescript msg hash script to compare with golang msghasher, right now failed to match, will check * identation * skip test for now * update comments * ts tidy * lint * lint * Implement bindings for TON as destination * Update bindings to match the new interface * changeset: Directly specify OnRamp address * fix broken test for all codecs * fix make errors * add gasLimit back from both on-chain and off-chain * remove offramp tolk gasLimit usage for now, keep the types in binding but not using it. Adding proper cell building helper function, need fix it and re-enable test * update comments * update * Remove SourceChains config from deploy step * changeset: Implement setOCR3Config * Support setting multiple dest chain configs at once on fee quoter too * changeset: Fix set offramp sources, test source config fetching * Address a lint * Handle empty sourceChainConfigs * add debug prints * Accessor: EVM2TON Event Queries (#141) * Implement bindings for TON as destination * chore: marking TODOs * feat: offramp event query wip * fix: wrong type * modify commitReportAccepted event, assume no batching, add test * chore: clean up * feat: binding for CommitReportAccepted * chore: queries wip * chore: CommitReportsGTETimestamp wip --------- Co-authored-by: Blaž Hrastnik <blaz@mxxn.io> Co-authored-by: vicente <vicentevieytes@hotmail.com> * feat: convert price updates * chore: clean up processMerkleRoot * chore: comment * fix: accessor Sync event filter behavior * fix test * add offramp binding and contract transmitter * rm empty test * chore: commit attempt * fix: commit report binding, deployer as transmitter * chore: debugging * enable contract transmitter in ccip provider, and return commit ct by default, need to have a new flag to differentiate commit vs execute. * chore: adding debug lines * Implement offramp config fetching * Read OnRamp address * Bump chainlink-common * Workaround minSeqNr not being updated * update ccip provider to use new relay args for contract transmitter, bump common version * chore: tidy * chore: missing event wip * fix: replay in goroutine * Update chainlink-common * Follow EVM flow and return zero values for some getters * Fix transmitter FromAddress * fix: add replay ctx * Move test key funding to deployment/ rather than integration-tests * Fix various transmitter bugs * Fix wallet tx signing * Reorder signature format so we match Aptos/keystore implementation * Use the right error code for GetChainFeePriceUpdate * Proper zero value * Fix CommitReportAccepted event name * Ignore events with no merkle roots * Fix Nonces() * Fix ocr3base tests * chore: CommitReportsGTETimestamp query TDD * fix: filter report with merkle root only * update * add comments back * fix test and add comments * small fix (#160) * [NONEVM-2514] Parallelize Jetton tests + Deploy MyLocalTon once (#161) * cicd: pararelize jetton tests * fix: test count test * fix: golint * fix: golint * fix: assert * Relayer: LogPoller lookback window (#158) * feat: logpoller lookback window * chore: update default config * chore: clean up, add unit tests * fix: name, lookback window calc * chore: clean up * fix: temp lookback window in replay * chore: clean up * fix: remove url logging in the relay * [NONEVM-2515] Parallelize tracetracker tests (#162) * test: reduce time of integration tests * Improve Nix devex - add lock-nix-tidy bin (#157) * Improve Nix devex - add lock-nix-tidy bin * Polish the script * Update docs * Update lock.nix docs * Refactor, extract generic types to remove chainlink/deployment dependency (#164) * Refactor, extract generic types to remove chainlink/deployment dependency * Use a single ChainDefinition type * Remove AddressBook * Derive chain family from the chain selector * More logging * Better logs * Bump core version * cicd: Stop running short tests (#165) * Fix: this was removed * Fix build * fix: onramp event gobindings, testtoken, accessor unit tests * feat: accessor ExecutedMessages, ExecutionStateChanged binding * Remove OnRampAddr from the RampMessageHeader * fix: remove separate binding * PriceReader impl * remove yubisneeze * Test helpers should throw so we get a nice diff * Get tests to pass again * Pack tokenPrices so they fit in a single slot * fix: accessor test * chore: nix hash * Allow some of the matches to fail * prettier -w . * Update go.md (I had to do it by hand?) * Resolve most golangci-lints * Fix test, min seq nr gets set to 1 once chain is configured * go.md * fix failing test * Properly implement the updateSourceChainConfig logic * Update core_version * Remove unused imports * chore: remove iterative onramp tests * Bump core version... * Fix: need to mark as always disabled * Build contracts and use the local contract version * Bump core_version * Fix symlink * Fix tests * missing quote * chore: remove RawOutgoingExternalMessages * update test helper wallet message mode * test: merge duplicated tests * fix build * check what's under chainlink * rm symlink as it already defined --------- Co-authored-by: Joe Huang <joe.huang@smartcontract.com> Co-authored-by: Jonghyeon Park <jadepark.dev@gmail.com> Co-authored-by: vicente <vicentevieytes@hotmail.com> Co-authored-by: Nicolas Mouso <nicolasgnr@gmail.com> Co-authored-by: Patricio <contact@patricios.space> Co-authored-by: Kristijan Rebernisak <kristijan.rebernisak@gmail.com> Co-authored-by: Oliver Townsend <oliver.townsend@smartcontract.com> Co-authored-by: Patricio Tourne Passarino <patricio.passarino@smartcontract.com>
1 parent 38a9dbd commit e6a9d88

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+3110
-745
lines changed

.github/workflows/ccip-integration-test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,14 @@ jobs:
111111
ref: ${{ steps.read_core_ref.outputs.CORE_REF }}
112112
path: chainlink
113113

114+
- name: Build contracts
115+
run: |
116+
cd contracts
117+
nix develop .#contracts -c yarn && yarn build
118+
114119
- name: Setup Environment and Run Tests
115120
run: |
121+
export CCIP_CONTRACTS_TON_VERSION="local"
116122
nix develop .#ccip-e2e -c scripts/e2e/setup-env.sh --core-dir "${GITHUB_WORKSPACE}/chainlink"
117123
nix develop .#ccip-e2e -c scripts/e2e/run-test.sh --core-dir "${GITHUB_WORKSPACE}/chainlink" --test-command "${{ matrix.type.cmd }}"
118124

cmd/chainlink-ton/lock.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Notice: `pkgs.lib.fakeHash` can be used as a placeholder,
22
# but `nix-lock-tidy` will only replace actual hashes.
33
{pkgs}: {
4-
chainlink-ton = "sha256-NuuQpPz2aKbO430qgRaQ0ksRY4rWpdisCr9vmgcbQcc=";
4+
chainlink-ton = "sha256-NVVBA3iQKCeJbm9r1ndPoD+SH2lZxhY1SiM5S0ElkAw=";
55
}

contracts/contracts/ccip/fee_quoter.tolk

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ struct DestChainConfig {
5858
type Msg = UpdatePrices
5959
| UpdateFeeTokens
6060
| UpdateTokenTransferFeeConfigs
61-
| UpdateDestChainConfig
61+
| FeeQuoterUpdateDestChainConfigs
6262
| GetValidatedFee<cell>; // marked as cell since we never attempt to load the metadata
6363

6464
fun onInternalMessage(in: InMessage) {
@@ -82,10 +82,10 @@ fun onInternalMessage(in: InMessage) {
8282
updateTokenTransferFeeConfigs(mutate st, msg);
8383
st.store();
8484
}
85-
UpdateDestChainConfig => {
85+
FeeQuoterUpdateDestChainConfigs => {
8686
var st = lazy Storage.load();
8787
st.ownable.requireOwner(in.senderAddress);
88-
updateDestChainConfig(mutate st, msg); // TODO: pass in st.destChainConfigs instead
88+
updateDestChainConfigs(mutate st, msg.updates); // TODO: pass in st.destChainConfigs instead
8989
st.store();
9090
}
9191
GetValidatedFee<cell> => { getValidatedFee(msg, in.senderAddress) }
@@ -139,22 +139,28 @@ fun updateTokenTransferFeeConfigs(mutate st: Storage, msg: UpdateTokenTransferFe
139139
}
140140

141141
// TODO: allow providing an array of these msgs to feeQuoter
142-
fun updateDestChainConfig(mutate st: Storage, msg: UpdateDestChainConfig) {
143-
val (maybeDestChainConfig, exists) = st.destChainConfigs.get(msg.destChainSelector);
144-
if (exists) {
145-
var destChainConfig = maybeDestChainConfig!;
146-
destChainConfig.config = msg.destChainConfig;
147-
st.destChainConfigs.replace(msg.destChainSelector, destChainConfig);
148-
} else {
149-
st.destChainConfigs.set(msg.destChainSelector, DestChainConfig {
150-
config: msg.destChainConfig,
151-
usdPerUnitGas: GasPrice {
152-
executionGasPrice: 0,
153-
dataAvailabilityGasPrice: 0,
154-
timestamp: 0,
155-
}.toCell(),
156-
tokenTransferFeeConfigs: Map<TokenTransferFeeConfig>.new()
157-
})
142+
fun updateDestChainConfigs(mutate st: Storage, updates: cell) {
143+
var iter = Iterator<FeeQuoterUpdateDestChainConfig>.new(updates);
144+
145+
while (!iter.empty()) {
146+
val update = iter.next();
147+
// create or update entries
148+
val (maybeDestChainConfig, exists) = st.destChainConfigs.get(update.destChainSelector);
149+
if (exists) {
150+
var destChainConfig = maybeDestChainConfig!;
151+
destChainConfig.config = update.destChainConfig;
152+
st.destChainConfigs.replace(update.destChainSelector, destChainConfig);
153+
} else {
154+
st.destChainConfigs.set(update.destChainSelector, DestChainConfig {
155+
config: update.destChainConfig,
156+
usdPerUnitGas: GasPrice {
157+
executionGasPrice: 0,
158+
dataAvailabilityGasPrice: 0,
159+
timestamp: 0,
160+
}.toCell(),
161+
tokenTransferFeeConfigs: Map<TokenTransferFeeConfig>.new()
162+
})
163+
}
158164
}
159165
}
160166

@@ -358,21 +364,22 @@ get fun tokenPrice(token: address): TimestampedPrice {
358364
// vec<TimestampedPrice>
359365
get fun tokenPrices(tokens: tuple): tuple {
360366
val st = lazy Storage.load();
361-
362-
var list: tuple? = null;
363-
364-
// TODO: are we able to get dynamic tuple inputs in getters?
367+
var list: tuple = createEmptyTuple();
365368
var iter = TupleIterator<address>.new(tokens);
366369

367370
while(!iter.empty()) {
368371
val token = iter.next();
369-
list = listPrepend(token, list);
372+
// NOTE: Need to skip missing items as nil entries rather than fail the query
373+
val (maybePrice, _exists) = st.usdPerToken.get(token);
374+
list.push(maybePrice.toCell());
370375
}
371-
return list!;
376+
return list;
372377
}
373378

374-
get fun destinationChainGasPrice(destChainSelector: uint64) {
375-
379+
get fun destinationChainGasPrice(destChainSelector: uint64): cell {
380+
val st = lazy Storage.load();
381+
val config = st.destChainConfigs.mustGet(destChainSelector, ERROR_UNKNOWN_DEST_CHAIN_SELECTOR);
382+
return config.usdPerUnitGas
376383
}
377384

378385
get fun tokenAndGasPrices(token: address, destChainSelector: uint64) {

contracts/contracts/ccip/offramp.tolk

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import "types.tolk";
2-
import "../lib/access/ownable_2step.tolk";
3-
import "../lib/upgrades/type_and_version.tolk";
1+
import "types.tolk"
2+
import "../deployable/types.tolk"
3+
import "../lib/access/ownable_2step.tolk"
44
import "../lib/crypto/merkle_multi_proof.tolk"
5-
import "../deployable/types.tolk";
6-
import "../lib/utils.tolk";
7-
import "../lib/ocr/multi_ocr3_base";
8-
import "../lib/ocr/types";
5+
import "../lib/ocr/multi_ocr3_base"
6+
import "../lib/ocr/types"
7+
import "../lib/upgrades/type_and_version.tolk"
8+
import "../lib/utils.tolk"
99

1010
struct Storage {
1111
id: uint32;
@@ -27,6 +27,20 @@ struct Storage {
2727
latestPriceSequenceNumber: uint64;
2828
}
2929

30+
struct Config {
31+
// -- Static config
32+
chainSelector: uint64
33+
// gasForCallExactCheck
34+
// rmnRemote
35+
// nonceManager
36+
// tokenAdminRegistry
37+
// -- Dynamic config
38+
feeQuoter: address
39+
permissionlessExecutionThresholdSeconds: uint32
40+
// isRMNVerificationDisabled
41+
// messageInterceptor
42+
}
43+
3044
struct SourceChainConfig {
3145
router: address;
3246
isEnabled: bool;
@@ -68,11 +82,11 @@ struct AlreadyAttempted {
6882
sequenceNumber: uint64;
6983
}
7084

71-
const CCIP_COMMIT_REPORT_ACCEPTED_TOPIC: int = stringCrc32("CCIPCommitReportAccepted");
85+
const CCIP_COMMIT_REPORT_ACCEPTED_TOPIC: int = stringCrc32("CommitReportAccepted");
7286

7387
struct CommitReportAccepted {
88+
merkleRoot: MerkleRoot? // vec<MerkleRoots>
7489
priceUpdates: Cell<PriceUpdates>?;
75-
merkleRoots: cell // vec<MerkleRoots>
7690
}
7791

7892
struct SkippedReportExecution {
@@ -231,11 +245,9 @@ fun _ccipReceiveConfirm(msg: OffRamp_CCIPReceiveConfirm, sender: address) {
231245

232246
// TODO: handle signals from MerkleRoot for releaseTokens
233247
// maybe send these and MerkleRoot_Execute straight to router
234-
235248
// the payload would need the id & chainSelector so we can calculate/validate offrampAddress & that the merkle root is allowed to send for that ID
236249
// NOTE: this assumes router has a well known, static address. So two stage deployment: put up router, then compile the addr into contracts
237250
// (or init contract state with that router addr? probably what solana does)
238-
239251
//TODO: This function should relay to the router or the message should be received directly by the router
240252
fun _dispatchValidated(msg: OffRamp_DispatchValidated, sender: address) {
241253
val st = Storage.load();
@@ -317,18 +329,21 @@ fun _commit(msg: OffRamp_Commit, sender: address) {
317329
}
318330

319331
var roots = Iterator<MerkleRoot>.new(report.merkleRoots);
320-
332+
val priceUpdatesOnlyReport = roots.empty();
321333
while (!roots.empty()) {
322334
val root = roots.next();
323335

324336
// validate lane not cursed
325337

326-
val sourceChainConfig = st.sourceChainConfigs.mustGet(root.sourceChainSelector, ERROR_SOURCE_CHAIN_NOT_ENABLED);
338+
var sourceChainConfig = st.sourceChainConfigs.mustGet(root.sourceChainSelector, ERROR_SOURCE_CHAIN_NOT_ENABLED);
327339

328-
assert(sourceChainConfig.isEnabled,ERROR_SOURCE_CHAIN_NOT_ENABLED);
340+
assert(sourceChainConfig.isEnabled, ERROR_SOURCE_CHAIN_NOT_ENABLED);
329341

330342
// validate against sourceChainConfig etc
343+
331344
// increment seqNr
345+
sourceChainConfig.minSeqNr += 1;
346+
st.sourceChainConfigs.replace(root.sourceChainSelector, sourceChainConfig);
332347

333348
val rootId = getMerkleRootID(root.merkleRoot);
334349
// initialize MerkleRoot subcontract
@@ -371,11 +386,14 @@ fun _commit(msg: OffRamp_Commit, sender: address) {
371386
msg.signatures
372387
);
373388

374-
emit(CCIP_COMMIT_REPORT_ACCEPTED_TOPIC, CommitReportAccepted {
375-
priceUpdates: report.priceUpdates,
376-
merkleRoots: report.merkleRoots,
377-
});
378-
389+
var root: MerkleRoot? = null;
390+
if (!priceUpdatesOnlyReport) {
391+
root = MerkleRoot.fromCell(report.merkleRoots);
392+
}
393+
emit(
394+
CCIP_COMMIT_REPORT_ACCEPTED_TOPIC,
395+
CommitReportAccepted { merkleRoot: root, priceUpdates: report.priceUpdates }
396+
);
379397
}
380398

381399
fun _execute(msg: OffRamp_Execute, sender: address) {
@@ -452,11 +470,29 @@ fun _execute(msg: OffRamp_Execute, sender: address) {
452470
fun _updateSourceChainConfig(msg: OffRamp_UpdateSourceChainConfig, sender: address) {
453471
var st = lazy Storage.load();
454472
st.ownable.requireOwner(sender);
455-
st.sourceChainConfigs.set(msg.sourceChainSelector, msg.config);
473+
var config: SourceChainConfig;
474+
var (existingConfig, exists) = st.sourceChainConfigs.get(msg.sourceChainSelector);
475+
if (!exists) {
476+
config = SourceChainConfig {
477+
router: msg.config.router,
478+
isEnabled: msg.config.isEnabled,
479+
minSeqNr: 1,
480+
isRMNVerificationDisabled: true, // RMN verification is always disabled
481+
onRamp: msg.config.onRamp,
482+
};
483+
} else {
484+
// OnRamp updates should only happen due to a misconfiguration.
485+
// If an OnRamp is misconfigured, no reports should have been
486+
// committed and no messages should have been executed.
487+
config = existingConfig!;
488+
assert(config.minSeqNr == 1 || config.onRamp.bitsEqual(msg.config.onRamp)) throw ERROR_INVALID_ON_RAMP_UPDATE;
489+
config.isEnabled = msg.config.isEnabled;
490+
config.onRamp = msg.config.onRamp;
491+
}
492+
st.sourceChainConfigs.set(msg.sourceChainSelector, config);
456493
st.store();
457494
}
458495

459-
460496
@pure @inline
461497
fun getMerkleRootID(merkleRoot: uint256): builder {
462498
var cs = beginCell().storeUint(merkleRoot, 256).endCell().beginParse();
@@ -467,26 +503,30 @@ fun getMerkleRootID(merkleRoot: uint256): builder {
467503
// get fun executionState() {
468504
// // TODO: node will need to directly look at MerkleRoot subcontracts
469505
// }
470-
471506
// get fun merkleRoot() {
472507
// // TODO: node will need to directly look at MerkleRoot subcontracts
473508
// }
474-
475-
get fun latestPriceSequenceNumber() {
476-
477-
}
478-
479-
get fun staticConfig() {
480-
// chainSelector, gasForCallExactCheck?, rmnRemote, nonceManager, tokenAdminRegistry
509+
get fun latestPriceSequenceNumber(): uint64 {
510+
var st = lazy Storage.load();
511+
return st.latestPriceSequenceNumber
481512
}
482513

483-
484514
fun setDynamicConfig() {
485515

486516
}
487517

488-
get fun dynamicConfig() {
489-
// TODO: combine get staticConfig and dynamicConfig into get config
518+
get fun ocr3Config(): OCR3Base {
519+
var st = lazy Storage.load();
520+
return st.ocr3Base.load();
521+
}
522+
523+
get fun config(): Config {
524+
val st = lazy Storage.load();
525+
return {
526+
chainSelector: st.chainSelector,
527+
feeQuoter: st.feeQuoter,
528+
permissionlessExecutionThresholdSeconds: st.permissionlessExecutionThresholdSeconds,
529+
}
490530
}
491531

492532
get fun sourceChainConfig(sourceChainSelector: uint64): SourceChainConfig {

contracts/contracts/ccip/types.tolk

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ struct Any2TVMRampMessage {
4242
sender: Cell<CrossChainAddress>;
4343
data: cell;
4444
receiver: address;
45-
//gasLimit: coins;
45+
// gasLimit: coins;
4646
tokenAmounts: cell?; // vec<Any2TVMTokenTransfer>
4747
// maybe mark these amounts as slice remaining then parse them by hand to avoid requiring this to be a map<> at send time?
4848
}
@@ -92,7 +92,7 @@ fun Any2TVMRampMessage.generateMessageId(self, metadataHash: uint256): uint256 {
9292
.storeUint(self.header.messageId, 256)
9393
.storeAddress(self.receiver)
9494
.storeUint(self.header.sequenceNumber, 64)
95-
//.storeCoins(self.gasLimit)
95+
// .storeCoins(self.gasLimit)
9696
.storeUint(self.header.nonce, 64)
9797
.endCell()
9898
)
@@ -198,7 +198,7 @@ struct WithdrawFeeTokens {}
198198

199199
struct TimestampedPrice {
200200
value: uint224;
201-
timestamp: uint64;
201+
timestamp: uint64; // TODO: size down to uint32, was a mistake when porting
202202
}
203203

204204
struct PriceUpdates {
@@ -284,11 +284,15 @@ struct UpdateTokenTransferFeeConfig {
284284
remove: cell; // vector<address>
285285
}
286286

287-
struct (0x20000004) UpdateDestChainConfig {
287+
struct FeeQuoterUpdateDestChainConfig {
288288
destChainSelector: uint64;
289289
destChainConfig: FeeQuoterDestChainConfig;
290290
}
291291

292+
struct (0x20000004) FeeQuoterUpdateDestChainConfigs {
293+
updates: cell; // vec<FeeQuoterUpdateDestChainConfig>
294+
}
295+
292296
struct (0x20000005) GetValidatedFee<C> {
293297
msg: Cell<CCIPSend>;
294298
metadata: Cell<C>,
@@ -396,3 +400,4 @@ const ERROR_UNAUTHORIZED: int = 265;
396400
const ERROR_SOURCE_CHAIN_NOT_ENABLED: int = 266;
397401
const ERROR_EMPTY_EXECUTION_REPORT:int = 267;
398402
const ERROR_DISPATCH_NOT_FROM_MERKLE_ROOT: int = 268;
403+
const ERROR_INVALID_ON_RAMP_UPDATE: int = 269;

contracts/contracts/lib/ocr/multi_ocr3_base.tolk

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ struct (0x2B78359F) OCR3Base_SetOCR3Config {
5353

5454
struct OCR3Base {
5555
chainId: uint8; //TODO :Is this even necessary?
56-
commit: Cell<Config>?;
57-
execute: Cell<Config>?;
56+
commit: Cell<OCRConfig>?;
57+
execute: Cell<OCRConfig>?;
5858
}
5959

60-
fun OCR3Base.getConfig(self, ocrPluginType: uint16): Config? {
60+
fun OCR3Base.getConfig(self, ocrPluginType: uint16): OCRConfig? {
6161
if (ocrPluginType == OCR_PLUGIN_TYPE_COMMIT) {
6262
if (self.commit == null) {
6363
return null;
@@ -190,7 +190,7 @@ fun OCR3Base.verifySignature(
190190

191191
val (signerIndex, authorized) = signers.get(signer);
192192
assert(authorized, ERROR_UNAUTHORIZED_SIGNER);
193-
assert(isSignatureValid(hashedReport,signature.toCell().beginParse(), signer), ERROR_INVALID_SIGNATURE);
193+
assert(isSignatureValid(hashedReport,signature.toCell().beginParse().skipBits(256), signer), ERROR_INVALID_SIGNATURE);
194194

195195
assert(!seen.has(signerIndex!), ERROR_NON_UNIQUE_SIGNATURES);
196196
seen.set(signerIndex!);

0 commit comments

Comments
 (0)