Skip to content

feat(target_chains/ethereum): add tx fee to evm contract #2526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 31, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
DataSource,
SetDataSources,
} from "../governance_payload/SetDataSources";
import { SetTransactionFee } from "../governance_payload/SetTransactionFee";

test("GovernancePayload ser/de", (done) => {
jest.setTimeout(60000);
Expand Down Expand Up @@ -424,6 +425,12 @@ function governanceActionArb(): Arbitrary<PythGovernanceAction> {
Buffer.from(token),
);
});
} else if (header.action === "SetTransactionFee") {
return fc
.record({ v: fc.bigUintN(64), e: fc.bigUintN(64) })
.map(({ v, e }) => {
return new SetTransactionFee(header.targetChainId, v, e);
});
} else {
throw new Error("Unsupported action type");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const TargetAction = {
RequestGovernanceDataSourceTransfer: 5,
SetWormholeAddress: 6,
SetFeeInToken: 7,
SetTransactionFee: 8,
} as const;

export const EvmExecutorAction = {
Expand Down Expand Up @@ -46,6 +47,8 @@ export function toActionName(
return "SetWormholeAddress";
case 7:
return "SetFeeInToken";
case 8:
return "SetTransactionFee";
}
} else if (
deserialized.moduleId == MODULE_EVM_EXECUTOR &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { PythGovernanceActionImpl } from "./PythGovernanceAction";
import * as BufferLayout from "@solana/buffer-layout";
import * as BufferLayoutExt from "./BufferLayoutExt";
import { ChainName } from "../chains";

/** Set the transaction fee on the target chain to newFeeValue * 10^newFeeExpo */
export class SetTransactionFee extends PythGovernanceActionImpl {
static layout: BufferLayout.Structure<
Readonly<{ newFeeValue: bigint; newFeeExpo: bigint }>
> = BufferLayout.struct([
BufferLayoutExt.u64be("newFeeValue"),
BufferLayoutExt.u64be("newFeeExpo"),
]);

constructor(
targetChainId: ChainName,
readonly newFeeValue: bigint,
readonly newFeeExpo: bigint,
) {
super(targetChainId, "SetTransactionFee");
}

static decode(data: Buffer): SetTransactionFee | undefined {
const decoded = PythGovernanceActionImpl.decodeWithPayload(
data,
"SetTransactionFee",
SetTransactionFee.layout,
);
if (!decoded) return undefined;

return new SetTransactionFee(
decoded[0].targetChainId,
decoded[1].newFeeValue,
decoded[1].newFeeExpo,
);
}

encode(): Buffer {
return super.encodeWithPayload(SetTransactionFee.layout, {
newFeeValue: this.newFeeValue,
newFeeExpo: this.newFeeExpo,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
StarknetSetWormholeAddress,
} from "./SetWormholeAddress";
import { EvmExecute } from "./ExecuteAction";
import { SetTransactionFee } from "./SetTransactionFee";

/** Decode a governance payload */
export function decodeGovernancePayload(
Expand Down Expand Up @@ -73,6 +74,8 @@ export function decodeGovernancePayload(
}
case "Execute":
return EvmExecute.decode(data);
case "SetTransactionFee":
return SetTransactionFee.decode(data);
default:
return undefined;
}
Expand All @@ -86,5 +89,6 @@ export * from "./GovernanceDataSourceTransfer";
export * from "./SetDataSources";
export * from "./SetValidPeriod";
export * from "./SetFee";
export * from "./SetTransactionFee";
export * from "./SetWormholeAddress";
export * from "./ExecuteAction";
11 changes: 8 additions & 3 deletions target_chains/ethereum/contracts/contracts/pyth/Pyth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ abstract contract Pyth is
// In the accumulator update data a single update can contain
// up to 255 messages and we charge a singleUpdateFee per each
// message
return 255 * singleUpdateFeeInWei() * updateDataSize;
return
255 *
singleUpdateFeeInWei() *
updateDataSize +
transactionFeeInWei();
}

function getUpdateFee(
Expand Down Expand Up @@ -330,7 +334,8 @@ abstract contract Pyth is
function getTotalFee(
uint totalNumUpdates
) private view returns (uint requiredFee) {
return totalNumUpdates * singleUpdateFeeInWei();
return
(totalNumUpdates * singleUpdateFeeInWei()) + transactionFeeInWei();
}

function findIndexOfPriceId(
Expand Down Expand Up @@ -392,6 +397,6 @@ abstract contract Pyth is
}

function version() public pure returns (string memory) {
return "1.4.3";
return "1.4.4-alpha.1";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,8 @@ contract PythGetters is PythState {
function governanceDataSourceIndex() public view returns (uint32) {
return _state.governanceDataSourceIndex;
}

function transactionFeeInWei() public view returns (uint) {
return _state.transactionFeeInWei;
}
}
12 changes: 12 additions & 0 deletions target_chains/ethereum/contracts/contracts/pyth/PythGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ abstract contract PythGovernance is
address oldWormholeAddress,
address newWormholeAddress
);
event TransactionFeeSet(uint oldFee, uint newFee);

function verifyGovernanceVM(
bytes memory encodedVM
Expand Down Expand Up @@ -97,6 +98,8 @@ abstract contract PythGovernance is
parseSetWormholeAddressPayload(gi.payload),
encodedVM
);
} else if (gi.action == GovernanceAction.SetTransactionFee) {
setTransactionFee(parseSetTransactionFeePayload(gi.payload));
} else {
revert PythErrors.InvalidGovernanceMessage();
}
Expand Down Expand Up @@ -243,4 +246,13 @@ abstract contract PythGovernance is

emit WormholeAddressSet(oldWormholeAddress, address(wormhole()));
}

function setTransactionFee(
SetTransactionFeePayload memory payload
) internal {
uint oldFee = transactionFeeInWei();
setTransactionFeeInWei(payload.newFee);

emit TransactionFeeSet(oldFee, transactionFeeInWei());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ contract PythGovernanceInstructions {
SetFee, // 3
SetValidPeriod, // 4
RequestGovernanceDataSourceTransfer, // 5
SetWormholeAddress // 6
SetWormholeAddress, // 6
SetTransactionFee // 7
}

struct GovernanceInstruction {
Expand Down Expand Up @@ -77,6 +78,10 @@ contract PythGovernanceInstructions {
address newWormholeAddress;
}

struct SetTransactionFeePayload {
uint newFee;
}

/// @dev Parse a GovernanceInstruction
function parseGovernanceInstruction(
bytes memory encodedInstruction
Expand Down Expand Up @@ -220,4 +225,22 @@ contract PythGovernanceInstructions {
if (encodedPayload.length != index)
revert PythErrors.InvalidGovernanceMessage();
}

/// @dev Parse a SetTransactionFeePayload (action 7) with minimal validation
function parseSetTransactionFeePayload(
bytes memory encodedPayload
) public pure returns (SetTransactionFeePayload memory stf) {
uint index = 0;

uint64 val = encodedPayload.toUint64(index);
index += 8;

uint64 expo = encodedPayload.toUint64(index);
index += 8;

stf.newFee = uint256(val) * uint256(10) ** uint256(expo);

if (encodedPayload.length != index)
revert PythErrors.InvalidGovernanceMessage();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ contract PythSetters is PythState, IPythEvents {
function setGovernanceDataSourceIndex(uint32 newIndex) internal {
_state.governanceDataSourceIndex = newIndex;
}

function setTransactionFeeInWei(uint fee) internal {
_state.transactionFeeInWei = fee;
}
}
2 changes: 2 additions & 0 deletions target_chains/ethereum/contracts/contracts/pyth/PythState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ contract PythStorage {
// Mapping of cached price information
// priceId => PriceInfo
mapping(bytes32 => PythInternalStructs.PriceInfo) latestPriceInfo;
// Fee charged per transaction, in addition to per-update fees
uint transactionFeeInWei;
}
}

Expand Down
Loading
Loading