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
588 changes: 588 additions & 0 deletions bindings/generated/latest/ccip/feetreasury/feetreasury.go

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions contracts/ccip/fee-treasury/daml.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
sdk-version: 3.4.11
name: ccip-fee-treasury
source: daml
version: 2.0.0
build-options:
- "-Wno-crypto-text-is-alpha"
- "-Werror=unused-dependency"
data-dependencies:
- ../../dependencies/splice/splice-api-token-metadata-v1-1.0.0.dar
- ../../dependencies/splice/splice-api-token-holding-v1-1.0.0.dar
- ../../dependencies/splice/splice-api-token-transfer-instruction-v1-1.0.0.dar
dependencies:
- daml-prim
- daml-stdlib
- ../../chainlink/api/.daml/dist/chainlink-api-2.0.0.dar
- ../../mcms/api/.daml/dist/mcms-api-1.0.0.dar
- ../codec/.daml/dist/ccip-codec-v2-2.0.0.dar
137 changes: 137 additions & 0 deletions contracts/ccip/fee-treasury/daml/CCIP/FeeTreasury.daml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
-- Two-phase design:
-- 1. feeOwner -> MCMSFeeTreasury.AuthorizeFeeWithdrawal
-- Creates a FeeWithdrawalAuthorization capturing the approved policy (recipient,
-- instrument, amount cap, expiry).
-- 2. anyone -> FeeWithdrawalAuthorization.ExecuteFeeWithdrawal
-- Supplies live ContractIds (holdings, TransferFactory) + ExtraArgs (disclosed
-- mining-round context). Not subject to MCMS validateTargetCidsSize cap.
module CCIP.FeeTreasury where

import DA.Crypto.Text (byteCount)
import DA.Map qualified as Map
import DA.Time (addRelTime, seconds)

import Splice.Api.Token.HoldingV1 (Holding, InstrumentId(..))
import Splice.Api.Token.MetadataV1 (ExtraArgs, emptyMetadata)
import Splice.Api.Token.TransferInstructionV1 (
Transfer(..),
TransferFactory,
TransferFactory_Transfer(..),
TransferInstructionResult,
TransferInstructionResult_Output(..),
)

import Chainlink.InstanceAddress qualified as RawInstanceAddress
import Chainlink.InstanceAddress (assertValidInstanceId)

import MCMS.MCMSReceiver (MCMSReceiver(..), MCMSReceiverView(..))

import CCIP.FeeTreasuryTypes (AuthorizeFeeWithdrawalParams(..))
import CCIP.FeeTreasuryCodecGen (decodeAuthorizeFeeWithdrawalParamsAt)

template MCMSFeeTreasury
with
instanceId : Text
feeOwner : Party
mcmsController : Party
where
signatory feeOwner
observer mcmsController

ensure assertValidInstanceId instanceId

-- Nonconsuming: authorizing does not mutate the treasury. The single-use
-- capability is the created FeeWithdrawalAuthorization contract itself (its
-- ContractId), consumed by ExecuteFeeWithdrawal or WithdrawAuthorization.
nonconsuming choice AuthorizeFeeWithdrawal : ContractId FeeWithdrawalAuthorization
with
params : AuthorizeFeeWithdrawalParams
controller feeOwner
do
now <- getTime
let expiresAt = now `addRelTime` seconds params.validitySecs
create FeeWithdrawalAuthorization with
feeOwner
recipient = params.recipient
instrumentId = params.instrumentId
maxAmount = params.maxAmount
expiresAt

interface instance MCMSReceiver for MCMSFeeTreasury where
view = MCMSReceiverView with
mcmsController
instanceId

mcmsEntrypoint functionName operationData contractIds = do
let instAddr = RawInstanceAddress.makeText instanceId feeOwner
selfCid <- case Map.lookup instAddr contractIds of
None -> abort $ "E_SELF_CID_NOT_IN_MAP: " <> instAddr
Some cid -> pure (coerceContractId cid : ContractId MCMSFeeTreasury)

case functionName of
"AuthorizeFeeWithdrawal" -> do
case decodeAuthorizeFeeWithdrawalParamsAt operationData 0 of
None -> abort "E_INVALID_PARAMS: AuthorizeFeeWithdrawal"
Some (params, finalOffset) -> do
assertMsg "E_TRAILING_BYTES: AuthorizeFeeWithdrawal" (finalOffset == byteCount operationData)
_ <- exercise selfCid AuthorizeFeeWithdrawal with params
-- AuthorizeFeeWithdrawal is nonconsuming: the treasury CID is
-- unchanged, so the contractIds map is returned as-is.
pure contractIds
_ -> abort $ "E_UNKNOWN_FUNCTION: " <> functionName


-- | Single-use MCMS-approved authorization to withdraw fees from feeOwner.
-- Anyone can submit ExecuteFeeWithdrawal — security comes from the locked policy
-- (recipient, instrument, amount cap, expiry), not from restricting who calls it.
template FeeWithdrawalAuthorization
with
feeOwner : Party
recipient : Party
instrumentId : InstrumentId
maxAmount : Decimal
expiresAt : Time
where
signatory feeOwner
observer recipient

-- | Execute the MCMS-approved withdrawal with live CIDs and disclosed context.
choice ExecuteFeeWithdrawal : TransferInstructionResult
with
submitter : Party
transferFactoryCid : ContractId TransferFactory
inputHoldingCids : [ContractId Holding]
amount : Decimal
extraArgs : ExtraArgs
requestedAt : Time
controller submitter
do
now <- getTime
assertMsg "feetreasury: requestedAt is in the future" (requestedAt <= now)
assertMsg "feetreasury: withdrawal authorization expired" (now < expiresAt)
assertMsg "feetreasury: amount exceeds authorized cap" (amount <= maxAmount)
assertMsg "feetreasury: amount must be positive" (amount > 0.0)
assertMsg "feetreasury: input holdings must not be empty" (not (null inputHoldingCids))

result <- exercise transferFactoryCid TransferFactory_Transfer with
expectedAdmin = instrumentId.admin
transfer = Transfer with
sender = feeOwner
receiver = recipient
amount
instrumentId
requestedAt
executeBefore = expiresAt
inputHoldingCids
meta = emptyMetadata
extraArgs

case result.output of
TransferInstructionResult_Failed ->
abort "feetreasury: token transfer failed"
_ -> pure result

-- | Cancel an unused authorization (e.g. after expiry or policy change).
choice WithdrawAuthorization : ()
controller feeOwner
do pure ()
41 changes: 41 additions & 0 deletions contracts/ccip/fee-treasury/daml/CCIP/FeeTreasuryCodecGen.daml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
-- AUTO-GENERATED by go-daml codegen. DO NOT EDIT.
module CCIP.FeeTreasuryCodecGen where

import DA.Crypto.Text (BytesHex)
import DA.Optional (fromSome)

import CCIP.FeeTreasuryTypes (AuthorizeFeeWithdrawalParams(..))

import MCMS.Codec (
decodeDecimalAt,
decodeInt64At,
decodePartyAt,
encodeDecimal,
encodeInt64,
encodeParty
)

import CCIP.CodecV2 (decodeInstrumentId, encodeInstrumentId)


-- ===============================================
-- | AuthorizeFeeWithdrawalParams
-- ===============================================

encodeAuthorizeFeeWithdrawalParams : AuthorizeFeeWithdrawalParams -> BytesHex
encodeAuthorizeFeeWithdrawalParams params =
encodeParty params.recipient
<> encodeInstrumentId params.instrumentId
<> encodeDecimal params.maxAmount
<> fromSome (encodeInt64 params.validitySecs)

decodeAuthorizeFeeWithdrawalParamsAt : BytesHex -> Int -> Optional (AuthorizeFeeWithdrawalParams, Int)
decodeAuthorizeFeeWithdrawalParamsAt encoded offset = do
(recipient, offset) <- decodePartyAt encoded offset
(instrumentId, offset) <- decodeInstrumentId encoded offset
(maxAmount, offset) <- decodeDecimalAt encoded offset
(validitySecs, offset) <- decodeInt64At encoded offset
Some (AuthorizeFeeWithdrawalParams{..}, offset)



17 changes: 17 additions & 0 deletions contracts/ccip/fee-treasury/daml/CCIP/FeeTreasuryTypes.daml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- | Parameter types for MCMSFeeTreasury MCMS entrypoint functions.
-- AUTO-GENERATED codecs are derived from this file by `make generate-daml-codecs`.
module CCIP.FeeTreasuryTypes where

import Splice.Api.Token.HoldingV1 (InstrumentId)

-- | Params for the "AuthorizeFeeWithdrawal" MCMS function.
-- All fields are scalars so they can be safely encoded in the signed merkle leaf.
-- ContractIds (holdings, factory, mining-round context) are NOT included here —
-- they must be supplied at execution time via ExecuteFeeWithdrawal choice args.
data AuthorizeFeeWithdrawalParams = AuthorizeFeeWithdrawalParams
with
recipient : Party
instrumentId : InstrumentId
maxAmount : Decimal
validitySecs : Int
deriving (Eq, Show)
1 change: 1 addition & 0 deletions contracts/ccip/test/daml.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ data-dependencies:
- ../sender/.daml/dist/ccip-sender-v2-2.0.0.dar
- ../receiver/.daml/dist/ccip-receiver-v2-2.0.0.dar
- ../factory/.daml/dist/ccip-factory-v2-2.0.0.dar
- ../fee-treasury/.daml/dist/ccip-fee-treasury-2.0.0.dar
dependencies:
- daml-prim
- daml-stdlib
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module CCIP.FeeTreasuryTest.FeeTreasuryCodecTest where

import Daml.Script
import DA.Assert ((===))
import DA.Optional (fromSome)

import Splice.Api.Token.HoldingV1 (InstrumentId(..))

import CCIP.FeeTreasuryTypes (AuthorizeFeeWithdrawalParams(..))
import CCIP.FeeTreasuryCodecGen (
encodeAuthorizeFeeWithdrawalParams,
decodeAuthorizeFeeWithdrawalParamsAt,
)

testAuthorizeFeeWithdrawalParamsRoundtrip : Script ()
testAuthorizeFeeWithdrawalParamsRoundtrip = script do
recipient <- allocateParty "recipient"
admin <- allocateParty "admin"

let instrumentId = InstrumentId admin "FeeToken"
params = AuthorizeFeeWithdrawalParams with
recipient
instrumentId
maxAmount = 1000.0
validitySecs = 3600

encoded = encodeAuthorizeFeeWithdrawalParams params
decoded = fst (fromSome (decodeAuthorizeFeeWithdrawalParamsAt encoded 0))

decoded === params

-- Trailing-bytes check: decoding at wrong offset returns None
decodeAuthorizeFeeWithdrawalParamsAt encoded 1 === None
Loading
Loading