Skip to content

Commit b8f388e

Browse files
authored
Merge pull request #2526 from pyth-network/pyth-tx-fee
feat(target_chains/ethereum): add tx fee to evm contract
2 parents 05b7928 + 92f19b2 commit b8f388e

File tree

12 files changed

+727
-5
lines changed

12 files changed

+727
-5
lines changed

governance/xc_admin/packages/xc_admin_common/src/__tests__/GovernancePayload.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
DataSource,
3333
SetDataSources,
3434
} from "../governance_payload/SetDataSources";
35+
import { SetTransactionFee } from "../governance_payload/SetTransactionFee";
3536

3637
test("GovernancePayload ser/de", (done) => {
3738
jest.setTimeout(60000);
@@ -424,6 +425,12 @@ function governanceActionArb(): Arbitrary<PythGovernanceAction> {
424425
Buffer.from(token),
425426
);
426427
});
428+
} else if (header.action === "SetTransactionFee") {
429+
return fc
430+
.record({ v: fc.bigUintN(64), e: fc.bigUintN(64) })
431+
.map(({ v, e }) => {
432+
return new SetTransactionFee(header.targetChainId, v, e);
433+
});
427434
} else {
428435
throw new Error("Unsupported action type");
429436
}

governance/xc_admin/packages/xc_admin_common/src/governance_payload/PythGovernanceAction.ts

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const TargetAction = {
1616
RequestGovernanceDataSourceTransfer: 5,
1717
SetWormholeAddress: 6,
1818
SetFeeInToken: 7,
19+
SetTransactionFee: 8,
1920
} as const;
2021

2122
export const EvmExecutorAction = {
@@ -46,6 +47,8 @@ export function toActionName(
4647
return "SetWormholeAddress";
4748
case 7:
4849
return "SetFeeInToken";
50+
case 8:
51+
return "SetTransactionFee";
4952
}
5053
} else if (
5154
deserialized.moduleId == MODULE_EVM_EXECUTOR &&
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { PythGovernanceActionImpl } from "./PythGovernanceAction";
2+
import * as BufferLayout from "@solana/buffer-layout";
3+
import * as BufferLayoutExt from "./BufferLayoutExt";
4+
import { ChainName } from "../chains";
5+
6+
/** Set the transaction fee on the target chain to newFeeValue * 10^newFeeExpo */
7+
export class SetTransactionFee extends PythGovernanceActionImpl {
8+
static layout: BufferLayout.Structure<
9+
Readonly<{ newFeeValue: bigint; newFeeExpo: bigint }>
10+
> = BufferLayout.struct([
11+
BufferLayoutExt.u64be("newFeeValue"),
12+
BufferLayoutExt.u64be("newFeeExpo"),
13+
]);
14+
15+
constructor(
16+
targetChainId: ChainName,
17+
readonly newFeeValue: bigint,
18+
readonly newFeeExpo: bigint,
19+
) {
20+
super(targetChainId, "SetTransactionFee");
21+
}
22+
23+
static decode(data: Buffer): SetTransactionFee | undefined {
24+
const decoded = PythGovernanceActionImpl.decodeWithPayload(
25+
data,
26+
"SetTransactionFee",
27+
SetTransactionFee.layout,
28+
);
29+
if (!decoded) return undefined;
30+
31+
return new SetTransactionFee(
32+
decoded[0].targetChainId,
33+
decoded[1].newFeeValue,
34+
decoded[1].newFeeExpo,
35+
);
36+
}
37+
38+
encode(): Buffer {
39+
return super.encodeWithPayload(SetTransactionFee.layout, {
40+
newFeeValue: this.newFeeValue,
41+
newFeeExpo: this.newFeeExpo,
42+
});
43+
}
44+
}

governance/xc_admin/packages/xc_admin_common/src/governance_payload/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
StarknetSetWormholeAddress,
2121
} from "./SetWormholeAddress";
2222
import { EvmExecute } from "./ExecuteAction";
23+
import { SetTransactionFee } from "./SetTransactionFee";
2324

2425
/** Decode a governance payload */
2526
export function decodeGovernancePayload(
@@ -73,6 +74,8 @@ export function decodeGovernancePayload(
7374
}
7475
case "Execute":
7576
return EvmExecute.decode(data);
77+
case "SetTransactionFee":
78+
return SetTransactionFee.decode(data);
7679
default:
7780
return undefined;
7881
}
@@ -86,5 +89,6 @@ export * from "./GovernanceDataSourceTransfer";
8689
export * from "./SetDataSources";
8790
export * from "./SetValidPeriod";
8891
export * from "./SetFee";
92+
export * from "./SetTransactionFee";
8993
export * from "./SetWormholeAddress";
9094
export * from "./ExecuteAction";

target_chains/ethereum/contracts/contracts/pyth/Pyth.sol

+8-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ abstract contract Pyth is
8686
// In the accumulator update data a single update can contain
8787
// up to 255 messages and we charge a singleUpdateFee per each
8888
// message
89-
return 255 * singleUpdateFeeInWei() * updateDataSize;
89+
return
90+
255 *
91+
singleUpdateFeeInWei() *
92+
updateDataSize +
93+
transactionFeeInWei();
9094
}
9195

9296
function getUpdateFee(
@@ -330,7 +334,8 @@ abstract contract Pyth is
330334
function getTotalFee(
331335
uint totalNumUpdates
332336
) private view returns (uint requiredFee) {
333-
return totalNumUpdates * singleUpdateFeeInWei();
337+
return
338+
(totalNumUpdates * singleUpdateFeeInWei()) + transactionFeeInWei();
334339
}
335340

336341
function findIndexOfPriceId(
@@ -392,6 +397,6 @@ abstract contract Pyth is
392397
}
393398

394399
function version() public pure returns (string memory) {
395-
return "1.4.3";
400+
return "1.4.4-alpha.1";
396401
}
397402
}

target_chains/ethereum/contracts/contracts/pyth/PythGetters.sol

+4
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,8 @@ contract PythGetters is PythState {
9191
function governanceDataSourceIndex() public view returns (uint32) {
9292
return _state.governanceDataSourceIndex;
9393
}
94+
95+
function transactionFeeInWei() public view returns (uint) {
96+
return _state.transactionFeeInWei;
97+
}
9498
}

target_chains/ethereum/contracts/contracts/pyth/PythGovernance.sol

+12
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ abstract contract PythGovernance is
3838
address oldWormholeAddress,
3939
address newWormholeAddress
4040
);
41+
event TransactionFeeSet(uint oldFee, uint newFee);
4142

4243
function verifyGovernanceVM(
4344
bytes memory encodedVM
@@ -97,6 +98,8 @@ abstract contract PythGovernance is
9798
parseSetWormholeAddressPayload(gi.payload),
9899
encodedVM
99100
);
101+
} else if (gi.action == GovernanceAction.SetTransactionFee) {
102+
setTransactionFee(parseSetTransactionFeePayload(gi.payload));
100103
} else {
101104
revert PythErrors.InvalidGovernanceMessage();
102105
}
@@ -243,4 +246,13 @@ abstract contract PythGovernance is
243246

244247
emit WormholeAddressSet(oldWormholeAddress, address(wormhole()));
245248
}
249+
250+
function setTransactionFee(
251+
SetTransactionFeePayload memory payload
252+
) internal {
253+
uint oldFee = transactionFeeInWei();
254+
setTransactionFeeInWei(payload.newFee);
255+
256+
emit TransactionFeeSet(oldFee, transactionFeeInWei());
257+
}
246258
}

target_chains/ethereum/contracts/contracts/pyth/PythGovernanceInstructions.sol

+24-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ contract PythGovernanceInstructions {
3434
SetFee, // 3
3535
SetValidPeriod, // 4
3636
RequestGovernanceDataSourceTransfer, // 5
37-
SetWormholeAddress // 6
37+
SetWormholeAddress, // 6
38+
SetTransactionFee // 7
3839
}
3940

4041
struct GovernanceInstruction {
@@ -77,6 +78,10 @@ contract PythGovernanceInstructions {
7778
address newWormholeAddress;
7879
}
7980

81+
struct SetTransactionFeePayload {
82+
uint newFee;
83+
}
84+
8085
/// @dev Parse a GovernanceInstruction
8186
function parseGovernanceInstruction(
8287
bytes memory encodedInstruction
@@ -220,4 +225,22 @@ contract PythGovernanceInstructions {
220225
if (encodedPayload.length != index)
221226
revert PythErrors.InvalidGovernanceMessage();
222227
}
228+
229+
/// @dev Parse a SetTransactionFeePayload (action 7) with minimal validation
230+
function parseSetTransactionFeePayload(
231+
bytes memory encodedPayload
232+
) public pure returns (SetTransactionFeePayload memory stf) {
233+
uint index = 0;
234+
235+
uint64 val = encodedPayload.toUint64(index);
236+
index += 8;
237+
238+
uint64 expo = encodedPayload.toUint64(index);
239+
index += 8;
240+
241+
stf.newFee = uint256(val) * uint256(10) ** uint256(expo);
242+
243+
if (encodedPayload.length != index)
244+
revert PythErrors.InvalidGovernanceMessage();
245+
}
223246
}

target_chains/ethereum/contracts/contracts/pyth/PythSetters.sol

+4
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,8 @@ contract PythSetters is PythState, IPythEvents {
4848
function setGovernanceDataSourceIndex(uint32 newIndex) internal {
4949
_state.governanceDataSourceIndex = newIndex;
5050
}
51+
52+
function setTransactionFeeInWei(uint fee) internal {
53+
_state.transactionFeeInWei = fee;
54+
}
5155
}

target_chains/ethereum/contracts/contracts/pyth/PythState.sol

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ contract PythStorage {
3838
// Mapping of cached price information
3939
// priceId => PriceInfo
4040
mapping(bytes32 => PythInternalStructs.PriceInfo) latestPriceInfo;
41+
// Fee charged per transaction, in addition to per-update fees
42+
uint transactionFeeInWei;
4143
}
4244
}
4345

0 commit comments

Comments
 (0)