Skip to content

Commit c73cada

Browse files
committed
Fees are now paid in the same mint as the intent
1 parent 735c8f4 commit c73cada

File tree

7 files changed

+43
-117
lines changed

7 files changed

+43
-117
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
filippo.io/edwards25519 v1.1.0
77
github.com/aws/aws-sdk-go-v2 v0.17.0
88
github.com/bits-and-blooms/bloom/v3 v3.1.0
9-
github.com/code-payments/code-protobuf-api v1.19.1-0.20250929133100-a1ab7623b991
9+
github.com/code-payments/code-protobuf-api v1.19.1-0.20251002160322-79d89fadd0fe
1010
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba
1111
github.com/emirpasic/gods v1.12.0
1212
github.com/envoyproxy/protoc-gen-validate v1.2.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
8080
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
8181
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
8282
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
83-
github.com/code-payments/code-protobuf-api v1.19.1-0.20250929133100-a1ab7623b991 h1:PHF+Z13T/RSzXygGLaDhl1ELArncjDnqWYCtlcLjdgw=
84-
github.com/code-payments/code-protobuf-api v1.19.1-0.20250929133100-a1ab7623b991/go.mod h1:fl4xu32MeNpGZR3wFhwEeKO0qVDuxYBNkqnvuADt6cA=
83+
github.com/code-payments/code-protobuf-api v1.19.1-0.20251002160322-79d89fadd0fe h1:f/jZbdX74ZNSRY9O+KRPNr3Cr3ukserXuImtsVzq+no=
84+
github.com/code-payments/code-protobuf-api v1.19.1-0.20251002160322-79d89fadd0fe/go.mod h1:fl4xu32MeNpGZR3wFhwEeKO0qVDuxYBNkqnvuADt6cA=
8585
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba h1:Bkp+gmeb6Y2PWXfkSCTMBGWkb2P1BujRDSjWeI+0j5I=
8686
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba/go.mod h1:jSiifpiBpyBQ8q2R0MGEbkSgWC6sbdRTyDBntmW+j1E=
8787
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=

pkg/code/currency/validation.go

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,15 @@ import (
55
"math"
66
"time"
77

8-
"github.com/pkg/errors"
9-
108
transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"
119

1210
"github.com/code-payments/code-server/pkg/code/common"
1311
code_data "github.com/code-payments/code-server/pkg/code/data"
1412
"github.com/code-payments/code-server/pkg/code/data/currency"
1513
currency_lib "github.com/code-payments/code-server/pkg/currency"
16-
"github.com/code-payments/code-server/pkg/database/query"
1714
"github.com/code-payments/code-server/pkg/solana/currencycreator"
1815
)
1916

20-
// GetPotentialClientCoreMintExchangeRates gets a set of fiat exchange rates that
21-
// a client attempting to maintain a latest state could have fetched from the
22-
// currency server for the core mint.
23-
func GetPotentialClientCoreMintExchangeRates(ctx context.Context, data code_data.Provider, code currency_lib.Code) ([]*currency.ExchangeRateRecord, error) {
24-
exchangeRecords, err := data.GetExchangeRateHistory(
25-
ctx,
26-
code,
27-
query.WithStartTime(time.Now().Add(-30*time.Minute)), // Give enough leeway to allow for 15 minute old rates
28-
query.WithEndTime(time.Now().Add(time.Minute)), // Ensure we pick up recent exchange rates
29-
query.WithLimit(32), // Try to pick up all records
30-
query.WithDirection(query.Descending), // Optimize for most recent records first
31-
)
32-
if err != nil && err != currency.ErrNotFound {
33-
return nil, err
34-
}
35-
36-
// To handle cases where the exchange rate worker might be down, try
37-
// loading the latest rate as clients would have and add it to valid
38-
// set of comparable records.
39-
latestExchangeRecord, err := data.GetExchangeRate(
40-
ctx,
41-
code,
42-
GetLatestExchangeRateTime(),
43-
)
44-
if err != nil && err != currency.ErrNotFound {
45-
return nil, err
46-
}
47-
48-
if latestExchangeRecord != nil {
49-
exchangeRecords = append(exchangeRecords, latestExchangeRecord)
50-
}
51-
52-
// This is bad, and means we can't query for any records
53-
if len(exchangeRecords) == 0 {
54-
return nil, errors.Errorf("found no exchange records for %s currency", code)
55-
}
56-
return exchangeRecords, nil
57-
}
58-
5917
// ValidateClientExchangeData validates proto exchange data provided by a client
6018
func ValidateClientExchangeData(ctx context.Context, data code_data.Provider, proto *transactionpb.ExchangeData) (bool, string, error) {
6119
mint, err := common.GetBackwardsCompatMint(proto.Mint)

pkg/code/server/transaction/action_handler.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ func NewNoPrivacyTransferActionHandler(ctx context.Context, data code_data.Provi
233233
}, nil
234234
}
235235

236-
func NewFeePaymentActionHandler(ctx context.Context, data code_data.Provider, protoAction *transactionpb.FeePaymentAction, feeCollector *common.Account) (CreateActionHandler, error) {
236+
func NewFeePaymentActionHandler(ctx context.Context, data code_data.Provider, protoAction *transactionpb.FeePaymentAction, feeCollectorOwner *common.Account) (CreateActionHandler, error) {
237237
mint, err := common.GetBackwardsCompatMint(protoAction.Mint)
238238
if err != nil {
239239
return nil, err
@@ -254,9 +254,14 @@ func NewFeePaymentActionHandler(ctx context.Context, data code_data.Provider, pr
254254
return nil, err
255255
}
256256

257+
destination, err := feeCollectorOwner.ToAssociatedTokenAccount(mint)
258+
if err != nil {
259+
return nil, err
260+
}
261+
257262
return &NoPrivacyTransferActionHandler{
258263
source: source,
259-
destination: feeCollector,
264+
destination: destination,
260265
amount: protoAction.Amount,
261266
feeType: protoAction.Type,
262267
}, nil

pkg/code/server/transaction/config.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const (
2121
ClientReceiveTimeoutConfigEnvName = envConfigPrefix + "CLIENT_RECEIVE_TIMEOUT"
2222
defaultClientReceiveTimeout = time.Second
2323

24-
FeeCollectorTokenPublicKeyConfigEnvName = envConfigPrefix + "FEE_COLLECTOR_TOKEN_PUBLIC_KEY"
24+
FeeCollectorOwnerPublicKeyConfigEnvName = envConfigPrefix + "FEE_COLLECTOR_OWNER_PUBLIC_KEY"
2525
defaultFeeCollectorPublicKey = "invalid" // Ensure something valid is set
2626

2727
CreateOnSendWithdrawalUsdFeeConfigEnvName = envConfigPrefix + "CREATE_ON_SEND_WITHDRAWAL_USD_FEE"
@@ -44,7 +44,7 @@ type conf struct {
4444
disableBlockchainChecks config.Bool // To avoid blockchain checks during testing
4545
submitIntentTimeout config.Duration
4646
clientReceiveTimeout config.Duration
47-
feeCollectorTokenPublicKey config.String
47+
feeCollectorOwnerPublicKey config.String
4848
createOnSendWithdrawalUsdFee config.Float64
4949
enableAirdrops config.Bool
5050
airdropperOwnerPublicKey config.String
@@ -64,7 +64,7 @@ func WithEnvConfigs() ConfigProvider {
6464
disableBlockchainChecks: wrapper.NewBoolConfig(memory.NewConfig(false), false),
6565
submitIntentTimeout: env.NewDurationConfig(SubmitIntentTimeoutConfigEnvName, defaultSubmitIntentTimeout),
6666
clientReceiveTimeout: env.NewDurationConfig(ClientReceiveTimeoutConfigEnvName, defaultClientReceiveTimeout),
67-
feeCollectorTokenPublicKey: env.NewStringConfig(FeeCollectorTokenPublicKeyConfigEnvName, defaultFeeCollectorPublicKey),
67+
feeCollectorOwnerPublicKey: env.NewStringConfig(FeeCollectorOwnerPublicKeyConfigEnvName, defaultFeeCollectorPublicKey),
6868
createOnSendWithdrawalUsdFee: env.NewFloat64Config(CreateOnSendWithdrawalUsdFeeConfigEnvName, defaultCreateOnSendWithdrawalUsdFee),
6969
enableAirdrops: env.NewBoolConfig(EnableAirdropsConfigEnvName, defaultEnableAirdrops),
7070
airdropperOwnerPublicKey: env.NewStringConfig(AirdropperOwnerPublicKeyEnvName, defaultAirdropperOwnerPublicKey),
@@ -79,7 +79,7 @@ type testOverrides struct {
7979
enableAmlChecks bool
8080
enableAirdrops bool
8181
clientReceiveTimeout time.Duration
82-
feeCollectorTokenPublicKey string
82+
feeCollectorOwnerPublicKey string
8383
}
8484

8585
func withManualTestOverrides(overrides *testOverrides) ConfigProvider {
@@ -91,7 +91,7 @@ func withManualTestOverrides(overrides *testOverrides) ConfigProvider {
9191
disableBlockchainChecks: wrapper.NewBoolConfig(memory.NewConfig(true), true),
9292
submitIntentTimeout: wrapper.NewDurationConfig(memory.NewConfig(defaultSubmitIntentTimeout), defaultSubmitIntentTimeout),
9393
clientReceiveTimeout: wrapper.NewDurationConfig(memory.NewConfig(overrides.clientReceiveTimeout), defaultClientReceiveTimeout),
94-
feeCollectorTokenPublicKey: wrapper.NewStringConfig(memory.NewConfig(overrides.feeCollectorTokenPublicKey), defaultFeeCollectorPublicKey),
94+
feeCollectorOwnerPublicKey: wrapper.NewStringConfig(memory.NewConfig(overrides.feeCollectorOwnerPublicKey), defaultFeeCollectorPublicKey),
9595
createOnSendWithdrawalUsdFee: wrapper.NewFloat64Config(memory.NewConfig(defaultCreateOnSendWithdrawalUsdFee), defaultCreateOnSendWithdrawalUsdFee),
9696
enableAirdrops: wrapper.NewBoolConfig(memory.NewConfig(overrides.enableAirdrops), false),
9797
airdropperOwnerPublicKey: wrapper.NewStringConfig(memory.NewConfig(defaultAirdropperOwnerPublicKey), defaultAirdropperOwnerPublicKey),

pkg/code/server/transaction/intent_handler.go

Lines changed: 27 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -521,44 +521,27 @@ func (h *SendPublicPaymentIntentHandler) AllowCreation(ctx context.Context, inte
521521
}
522522

523523
//
524-
// Part 3: Exchange data validation
524+
// Part 3: Account validation to determine if it's managed by Code
525525
//
526526

527-
if err := validateExchangeDataWithinIntent(ctx, h.data, typedMetadata.Mint, typedMetadata.ExchangeData); err != nil {
527+
err = validateAllUserAccountsManagedByCode(ctx, initiatorAccounts)
528+
if err != nil {
528529
return err
529530
}
530531

531532
//
532-
// Part 4: Local simulation
533+
// Part 4: Exchange data validation
533534
//
534535

535-
simResult, err := LocalSimulation(ctx, h.data, actions)
536-
if err != nil {
536+
if err := validateExchangeDataWithinIntent(ctx, h.data, typedMetadata.Mint, typedMetadata.ExchangeData); err != nil {
537537
return err
538538
}
539539

540-
// Fee payments always operate against the core mint, so we need to add relevant
541-
// core mint initiator records
542-
if simResult.HasAnyFeePayments() && !common.IsCoreMint(intentMintAccount) {
543-
initiatorAccountsByType, ok := initiatorAccountsByMintAndType[common.CoreMintAccount.PublicKey().ToBase58()]
544-
if !ok {
545-
return errors.New("initiator core mint accounts don't exist")
546-
}
547-
primaryAccountRecords, ok := initiatorAccountsByType[commonpb.AccountType_PRIMARY]
548-
if !ok {
549-
return errors.New("initiator core mint primary account doesn't exist")
550-
}
551-
for _, records := range primaryAccountRecords {
552-
initiatorAccounts = append(initiatorAccounts, records)
553-
initiatorAccountsByVault[records.General.TokenAccount] = records
554-
}
555-
}
556-
557540
//
558-
// Part 5: Account validation to determine if it's managed by Code
541+
// Part 5: Local simulation
559542
//
560543

561-
err = validateAllUserAccountsManagedByCode(ctx, initiatorAccounts)
544+
simResult, err := LocalSimulation(ctx, h.data, actions)
562545
if err != nil {
563546
return err
564547
}
@@ -724,7 +707,7 @@ func (h *SendPublicPaymentIntentHandler) validateActions(
724707
return nil
725708
}
726709

727-
// Check whether the destination account is a core mint token account that's
710+
// Check whether the destination account is a token account of the same mint that's
728711
// been created on the blockchain. If not, a fee is required
729712
err = validateExternalTokenAccountWithinIntent(ctx, h.data, destination, intentMint)
730713
switch err {
@@ -756,7 +739,7 @@ func (h *SendPublicPaymentIntentHandler) validateActions(
756739

757740
//
758741
// Part 4.1: Check destination account is paid exact quark amount from the deposit account
759-
// minus any fees if we're operating against the core mint
742+
// minus any fees
760743
//
761744

762745
expectedDestinationPayment := int64(metadata.ExchangeData.Quarks)
@@ -768,9 +751,7 @@ func (h *SendPublicPaymentIntentHandler) validateActions(
768751
return NewIntentValidationError("expected at most 1 fee payment")
769752
}
770753
for _, feePayment := range feePayments {
771-
if common.IsCoreMint(intentMint) {
772-
expectedDestinationPayment += feePayment.DeltaQuarks
773-
}
754+
expectedDestinationPayment += feePayment.DeltaQuarks
774755
}
775756

776757
destinationSimulation, ok := simResult.SimulationsByAccount[destination.PublicKey().ToBase58()]
@@ -1584,8 +1565,6 @@ func validateMoneyMovementActionUserAccounts(
15841565
}
15851566
}
15861567
case *transactionpb.Action_FeePayment:
1587-
// Fee payments always come from the core mint primary account
1588-
15891568
mint, err = common.GetBackwardsCompatMint(typedAction.FeePayment.Mint)
15901569
if err != nil {
15911570
return err
@@ -1602,8 +1581,8 @@ func validateMoneyMovementActionUserAccounts(
16021581
}
16031582

16041583
sourceAccountInfo, ok := initiatorAccountsByVault[source.PublicKey().ToBase58()]
1605-
if !ok || sourceAccountInfo.General.AccountType != commonpb.AccountType_PRIMARY || sourceAccountInfo.General.MintAccount != common.CoreMintAccount.PublicKey().ToBase58() {
1606-
return NewActionValidationError(action, "source account must be the core mint primary account")
1584+
if !ok || sourceAccountInfo.General.AccountType != commonpb.AccountType_PRIMARY {
1585+
return NewActionValidationError(action, "source account must be the PRMARY account")
16071586
}
16081587
default:
16091588
continue
@@ -1760,11 +1739,6 @@ func validateFeePayments(
17601739
return err
17611740
}
17621741

1763-
// todo: Probably not always going to be the case, but add a strict validation to start
1764-
if !common.IsCoreMint(mintAccount) {
1765-
return NewActionValidationError(feePayment.Action, "fee payment must be made in core mint")
1766-
}
1767-
17681742
if feePaymentAction.Type != expectedFeeType {
17691743
return NewActionValidationErrorf(feePayment.Action, "expected a %s fee payment", expectedFeeType.String())
17701744
}
@@ -1783,23 +1757,20 @@ func validateFeePayments(
17831757
}
17841758
feeAmount = -feeAmount // Because it's coming out of a user account in this simulation
17851759

1786-
var foundUsdExchangeRecord bool
1787-
usdExchangeRecords, err := currency_util.GetPotentialClientCoreMintExchangeRates(ctx, data, currency_lib.USD)
1760+
mintQuarksPerUnit := common.GetMintQuarksPerUnit(mintAccount)
1761+
unitsOfMint := float64(feeAmount) / float64(mintQuarksPerUnit)
1762+
1763+
isValid, _, err := currency_util.ValidateClientExchangeData(ctx, data, &transactionpb.ExchangeData{
1764+
Currency: string(currency_lib.USD),
1765+
NativeAmount: expectedUsdValue,
1766+
ExchangeRate: expectedUsdValue / unitsOfMint,
1767+
Quarks: uint64(feeAmount),
1768+
Mint: mintAccount.ToProto(),
1769+
})
17881770
if err != nil {
17891771
return err
1790-
}
1791-
for _, exchangeRecord := range usdExchangeRecords {
1792-
usdValue := exchangeRecord.Rate * float64(feeAmount) / float64(common.CoreMintQuarksPerUnit)
1793-
1794-
// Allow for some small margin of error
1795-
if usdValue > expectedUsdValue-0.0001 && usdValue < expectedUsdValue+0.0001 {
1796-
foundUsdExchangeRecord = true
1797-
break
1798-
}
1799-
}
1800-
1801-
if !foundUsdExchangeRecord {
1802-
return NewActionValidationErrorf(feePayment.Action, "%s fee payment amount must be $%.2f USD", expectedFeeType.String(), expectedUsdValue)
1772+
} else if !isValid {
1773+
return NewActionValidationErrorf(feePayment.Action, "%s fee payment amount must be %.2f USD", expectedFeeType.String(), expectedUsdValue)
18031774
}
18041775

18051776
return nil
@@ -1963,7 +1934,6 @@ func validateTimelockUnlockStateDoesntExist(ctx context.Context, data code_data.
19631934

19641935
func validateIntentAndActionMintsMatch(intentMint *common.Account, actions []*transactionpb.Action) error {
19651936
for _, action := range actions {
1966-
var forceCoreMint bool
19671937
var actionMint *common.Account
19681938
var err error
19691939
switch typed := action.Type.(type) {
@@ -1974,23 +1944,16 @@ func validateIntentAndActionMintsMatch(intentMint *common.Account, actions []*tr
19741944
case *transactionpb.Action_NoPrivacyWithdraw:
19751945
actionMint, err = common.GetBackwardsCompatMint(typed.NoPrivacyWithdraw.Mint)
19761946
case *transactionpb.Action_FeePayment:
1977-
forceCoreMint = true // Fee payments are an exception, since they always operate against the core mint
19781947
actionMint, err = common.GetBackwardsCompatMint(typed.FeePayment.Mint)
19791948
default:
19801949
return errors.New("unsupported action for mint extraction")
19811950
}
1982-
19831951
if err != nil {
19841952
return err
19851953
}
1986-
if forceCoreMint {
1987-
if !common.IsCoreMint(actionMint) {
1988-
return NewActionValidationErrorf(action, "mint must be %s", common.CoreMintAccount.PublicKey().ToBase58())
1989-
}
1990-
} else {
1991-
if !bytes.Equal(intentMint.PublicKey().ToBytes(), actionMint.PublicKey().ToBytes()) {
1992-
return NewActionValidationErrorf(action, "mint must be %s", intentMint.PublicKey().ToBase58())
1993-
}
1954+
1955+
if !bytes.Equal(intentMint.PublicKey().ToBytes(), actionMint.PublicKey().ToBytes()) {
1956+
return NewActionValidationErrorf(action, "mint must be %s", intentMint.PublicKey().ToBase58())
19941957
}
19951958
}
19961959
return nil

pkg/code/server/transaction/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func NewTransactionServer(
8787
localAccountLocks: make(map[string]*sync.Mutex),
8888
}
8989

90-
s.feeCollector, err = common.NewAccountFromPublicKeyString(s.conf.feeCollectorTokenPublicKey.Get(ctx))
90+
s.feeCollector, err = common.NewAccountFromPublicKeyString(s.conf.feeCollectorOwnerPublicKey.Get(ctx))
9191
if err != nil {
9292
return nil, err
9393
}

0 commit comments

Comments
 (0)