Merge pull request #4 from wormhole-foundation/core-relayer/integration

Clean up send (#2)
This commit is contained in:
A5 Pickle 2022-10-06 12:19:04 -05:00 committed by GitHub
commit 684912a8ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 301 additions and 2754 deletions

View File

@ -6,6 +6,7 @@ all: build
build: sdk/node_modules build: sdk/node_modules
cd ethereum && make build cd ethereum && make build
cd sdk && npm run build cd sdk && npm run build
cd offchain-relayer && bash regenerate-abi.sh
sdk/node_modules: sdk/node_modules:
cd sdk && npm ci cd sdk && npm ci

View File

@ -61,20 +61,10 @@ struct DeliveryInstructions {
// Chain ID of the receiver // Chain ID of the receiver
ToChain uint16 ToChain uint16
// Length of user Payload
PayloadLen uint16
// Payload
Payload bytes
// Length of chain-specific payload
ChainPayloadLen uint16
// chain-specific delivery payload (accounts, storage slots...)
ChainPayload bytes
// Length of VAA whitelist // Length of VAA whitelist
WhitelistLen uint16 WhitelistLen uint16
// VAA whitelist // VAA whitelist
Whitelist []VAAId Whitelist []AllowedEmitterSequence
// Length of Relayer Parameters // Length of Relayer Parameters
RelayerParamsLen uint16 RelayerParamsLen uint16
@ -82,7 +72,7 @@ struct DeliveryInstructions {
RelayerParams bytes RelayerParams bytes
} }
struct VAAId { struct AllowedEmitterSequence {
// VAA emitter // VAA emitter
EmitterAddress bytes32 EmitterAddress bytes32
// VAA sequence // VAA sequence
@ -96,7 +86,7 @@ struct DeliveryStatus {
// Hash of the relayed batch // Hash of the relayed batch
BatchHash bytes32 BatchHash bytes32
// Delivery // Delivery
Delivery VAAId Delivery AllowedEmitterSequence
// Delivery try // Delivery try
DeliveryCount uint16 DeliveryCount uint16
@ -112,7 +102,7 @@ struct ReDeliveryInstructions {
// Hash of the batch to re-deliver // Hash of the batch to re-deliver
BatchHash bytes32 BatchHash bytes32
// Point to the original delivery instruction // Point to the original delivery instruction
OriginalDelivery VAAId OriginalDelivery AllowedEmitterSequence
// Current deliverycount // Current deliverycount
DeliveryCount uint16 DeliveryCount uint16
@ -136,14 +126,14 @@ A relaying system that handles payment on the source chain could have minimal Re
struct MinimalRelayerParamsExample { struct MinimalRelayerParamsExample {
// All payloads should be versioned // All payloads should be versioned
Version uint8 Version uint8
// Limit the gas amount forwarded to wormholeReceiver() // Limit the gas amount forwarded to receiveWormholeMessages()
DeliveryGasAmount uint32 DeliveryGasAmount uint32
} }
struct RelayerParamsExample { struct RelayerParamsExample {
// All payloads should be versioned // All payloads should be versioned
Version uint8 Version uint8
// Limit the gas amount forwarded to wormholeReceiver() // Limit the gas amount forwarded to receiveWormholeMessages()
DeliveryGasAmount uint32 DeliveryGasAmount uint32
// Limit the max batch size that was paid for -> fail delivery if batch is too big // Limit the max batch size that was paid for -> fail delivery if batch is too big
MaxVAAsInBatch bytes32 MaxVAAsInBatch bytes32
@ -156,7 +146,7 @@ struct RelayerParamsExample {
### Relaying Contract Endpoints ### Relaying Contract Endpoints
`send(uint16 targetChain, bytes32 targetAddress, bytes payload, VAAWhitelist VAAId[], bytes relayerParams, bytes[] chainPayload, uint32 nonce, uint8 consistencyLevel) payable` `send(uint16 targetChain, bytes32 targetAddress, bytes payload, VAAWhitelist AllowedEmitterSequence[], bytes relayerParams, bytes[] chainPayload, uint32 nonce, uint8 consistencyLevel) payable`
- Optionally: handle payment - Optionally: handle payment
- Emit `DeliveryInstructions` VAA with specified `targetChain, targetAddress, payload, relayerParams, nonce, VAAWhitelist` and `msg.sender`, the entry-points `chainId` - Emit `DeliveryInstructions` VAA with specified `targetChain, targetAddress, payload, relayerParams, nonce, VAAWhitelist` and `msg.sender`, the entry-points `chainId`
@ -166,19 +156,19 @@ struct RelayerParamsExample {
- Optionally: handle payment - Optionally: handle payment
- Emit `RedeliveryInstructions` VAA with the batch hash & delivery reference and new `relayerParams` - Emit `RedeliveryInstructions` VAA with the batch hash & delivery reference and new `relayerParams`
`estimateCost(uint16 targetChainId, bytes[] relayerParams) view` `estimateEvmCost(uint16 targetChainId, bytes[] relayerParams) view`
- provide a quote for given relayerParams - provide a quote for given relayerParams
- Gas Price, Limit should be encoded in `relayerParams` - Gas Price, Limit should be encoded in `relayerParams`
`deliver(VAAv2 batchVAA, VAAId delivery, uint targetCallGasOverwrite)` `deliver(VAAv2 batchVAA, AllowedEmitterSequence delivery, uint targetCallGasOverwrite)`
- Check the delivery hash (`hash(batchHash, VAAId)`) against already successfully delivered ones and revert if it was successfully delivered before - Check the delivery hash (`hash(batchHash, AllowedEmitterSequence)`) against already successfully delivered ones and revert if it was successfully delivered before
- Parse and verify VAAv2 at Wormhole contract with caching enabled - Parse and verify VAAv2 at Wormhole contract with caching enabled
- Check if the emitter of the `DeliveryInstructions` VAA is a known and trusted relaying contract - Check if the emitter of the `DeliveryInstructions` VAA is a known and trusted relaying contract
- Check if the `DeliveryInstructions.ToChain` is the right chain - Check if the `DeliveryInstructions.ToChain` is the right chain
- If a whitelist is specified, check if all specified VAAs are in the delivered VAAv2, if no whitelist is specified check that the full batch was delivered - If a whitelist is specified, check if all specified VAAs are in the delivered VAAv2, if no whitelist is specified check that the full batch was delivered
- Call `wormholeReceiver` on the `DeliveryInstructions.ToAddress` - Call `receiveWormholeMessages` on the `DeliveryInstructions.ToAddress`
- If the length of the VAA whitelist is > 0, just forward the whitelist of VAAs, otherwise the full batch - If the length of the VAA whitelist is > 0, just forward the whitelist of VAAs, otherwise the full batch
- If `targetCallGasOverwrite` is higher than what might be specified in `DeliveryInstructions.RelayerParams` take the overwrite value - If `targetCallGasOverwrite` is higher than what might be specified in `DeliveryInstructions.RelayerParams` take the overwrite value
- Emit `DeliveryStatus` depending on success or failure of the `receive` call - Emit `DeliveryStatus` depending on success or failure of the `receive` call
@ -195,7 +185,7 @@ struct RelayerParamsExample {
### Receiver Contract Endpoints ### Receiver Contract Endpoints
`wormholeReceiver(VAAv4[] vaas, uint16 sourceChain, bytes32 sourceAddress, bytes payload)` `receiveWormholeMessages(VAAv4[] vaas, uint16 sourceChain, bytes32 sourceAddress, bytes payload)`
- verify `msg.sender == relayer contract` - verify `msg.sender == relayer contract`
- verify `sourceAddress` and `sourceChain` - verify `sourceAddress` and `sourceChain`

View File

@ -12,10 +12,10 @@ contract CoreRelayer is CoreRelayerGovernance {
using BytesLib for bytes; using BytesLib for bytes;
/** /**
* @dev `estimateCost` computes the estimated cost of delivering a batch VAA to a target chain. * @dev `estimateEvmCost` computes the estimated cost of delivering a batch VAA to a target chain.
* it fetches the gas price in native currency for one unit of gas on the target chain * it fetches the gas price in native currency for one unit of gas on the target chain
*/ */
function estimateCost(uint16 chainId, uint256 gasLimit) public view returns (uint256 gasEstimate) { function estimateEvmCost(uint16 chainId, uint256 gasLimit) public view returns (uint256 gasEstimate) {
return (gasOracle().computeGasCost(chainId, gasLimit + evmDeliverGasOverhead()) + wormhole().messageFee()); return (gasOracle().computeGasCost(chainId, gasLimit + evmDeliverGasOverhead()) + wormhole().messageFee());
} }
@ -32,16 +32,8 @@ contract CoreRelayer is CoreRelayerGovernance {
// decode the relay parameters // decode the relay parameters
RelayParameters memory relayParams = decodeRelayParameters(deliveryParams.relayParameters); RelayParameters memory relayParams = decodeRelayParameters(deliveryParams.relayParameters);
require(relayParams.deliveryGasLimit > 0, "invalid deliveryGasLimit in relayParameters"); // estimate relay cost and check to see if the user sent enough eth to cover the relay
collectRelayerParameterPayment(relayParams, deliveryParams.targetChain, relayParams.deliveryGasLimit);
// Estimate the gas costs of the delivery, and confirm the user sent the right amount of gas.
// The user needs to make sure to send a little extra value to cover the wormhole messageFee on this chain.
uint256 deliveryCostEstimate = estimateCost(deliveryParams.targetChain, relayParams.deliveryGasLimit);
require(
relayParams.nativePayment == deliveryCostEstimate && msg.value == deliveryCostEstimate,
"insufficient fee specified in msg.value"
);
// sanity check a few of the values before composing the DeliveryInstructions // sanity check a few of the values before composing the DeliveryInstructions
require(deliveryParams.targetAddress != bytes32(0), "invalid targetAddress"); require(deliveryParams.targetAddress != bytes32(0), "invalid targetAddress");
@ -76,6 +68,8 @@ contract CoreRelayer is CoreRelayerGovernance {
incrementRedeliveryAttempt(deliveryHash); incrementRedeliveryAttempt(deliveryHash);
RelayParameters memory relayParams = decodeRelayParameters(newRelayerParams); RelayParameters memory relayParams = decodeRelayParameters(newRelayerParams);
// estimate relay cost and check to see if the user sent enough eth to cover the relay
collectRelayerParameterPayment(relayParams, vm.emitterChainId, relayParams.deliveryGasLimit); collectRelayerParameterPayment(relayParams, vm.emitterChainId, relayParams.deliveryGasLimit);
RedeliveryInstructions memory redeliveryInstructions = RedeliveryInstructions({ RedeliveryInstructions memory redeliveryInstructions = RedeliveryInstructions({
@ -98,13 +92,11 @@ contract CoreRelayer is CoreRelayerGovernance {
RelayParameters memory relayParams, RelayParameters memory relayParams,
uint16 targetChain, uint16 targetChain,
uint32 targetGasLimit uint32 targetGasLimit
) ) internal {
internal
{
require(relayParams.deliveryGasLimit > 0, "invalid deliveryGasLimit in relayParameters"); require(relayParams.deliveryGasLimit > 0, "invalid deliveryGasLimit in relayParameters");
// Estimate the gas costs of the delivery, and confirm the user sent the right amount of gas. // Estimate the gas costs of the delivery, and confirm the user sent the right amount of gas.
uint256 deliveryCostEstimate = estimateCost(targetChain, targetGasLimit); uint256 deliveryCostEstimate = estimateEvmCost(targetChain, targetGasLimit);
require( require(
relayParams.nativePayment == deliveryCostEstimate && msg.value == deliveryCostEstimate, relayParams.nativePayment == deliveryCostEstimate && msg.value == deliveryCostEstimate,
@ -118,8 +110,7 @@ contract CoreRelayer is CoreRelayerGovernance {
* it locates the DeliveryInstructions VAA in the batch * it locates the DeliveryInstructions VAA in the batch
* it checks to see if the batch has been delivered already * it checks to see if the batch has been delivered already
* it verifies that the delivery instructions were generated by a registered relayer contract * it verifies that the delivery instructions were generated by a registered relayer contract
* it verifies that the delivered batch contains all VAAs specified in the deliveryList (if it's a partial batch) * it forwards the array of VAAs in the batch to the target contract by calling the `receiveWormholeMessages` endpoint
* it forwards the array of VAAs in the batch to the target contract by calling the `wormholeReceiver` endpoint
* it records the specified relayer fees for the caller * it records the specified relayer fees for the caller
* it emits a DeliveryStatus message containing the results of the delivery * it emits a DeliveryStatus message containing the results of the delivery
*/ */
@ -127,28 +118,37 @@ contract CoreRelayer is CoreRelayerGovernance {
// cache wormhole instance // cache wormhole instance
IWormhole wormhole = wormhole(); IWormhole wormhole = wormhole();
// build InternalDelivery struct to reduce local variable count
InternalDeliveryParams memory internalParams;
// parse the batch VAA // parse the batch VAA
IWormhole.VM2 memory batchVM = wormhole.parseBatchVM(targetParams.encodedVM); internalParams.batchVM = wormhole.parseBatchVM(targetParams.encodedVM);
// cache the deliveryVM // cache the deliveryVM
IWormhole.VM memory deliveryVM = batchVM.indexedObservations[targetParams.deliveryIndex].vm3; IWormhole.VM memory deliveryVM =
parseWormholeObservation(internalParams.batchVM.observations[targetParams.deliveryIndex]);
require(verifyRelayerVM(deliveryVM), "invalid emitter"); require(verifyRelayerVM(deliveryVM), "invalid emitter");
// create the VAAId for the delivery VAA // create the AllowedEmitterSequence for the delivery VAA
VAAId memory deliveryId = VAAId({emitterAddress: deliveryVM.emitterAddress, sequence: deliveryVM.sequence}); internalParams.deliveryId =
AllowedEmitterSequence({emitterAddress: deliveryVM.emitterAddress, sequence: deliveryVM.sequence});
// parse the deliveryVM payload into the DeliveryInstructions struct // parse the deliveryVM payload into the DeliveryInstructions struct
DeliveryInstructions memory deliveryInstructions = decodeDeliveryInstructions(deliveryVM.payload); internalParams.deliveryInstructions = decodeDeliveryInstructions(deliveryVM.payload);
// parse the relayParams // parse the relayParams
RelayParameters memory relayParams = decodeRelayParameters(deliveryInstructions.relayParameters); internalParams.relayParams = decodeRelayParameters(internalParams.deliveryInstructions.relayParameters);
// Override the target gas if requested by the relayer // override the target gas if requested by the relayer
if (targetParams.targetCallGasOverride > relayParams.deliveryGasLimit) { if (targetParams.targetCallGasOverride > internalParams.relayParams.deliveryGasLimit) {
relayParams.deliveryGasLimit = targetParams.targetCallGasOverride; internalParams.relayParams.deliveryGasLimit = targetParams.targetCallGasOverride;
} }
return _deliver(wormhole, batchVM, deliveryInstructions, deliveryId, relayParams, 0); // set the remaining values in the InternalDeliveryParams struct
internalParams.deliveryIndex = targetParams.deliveryIndex;
internalParams.deliveryAttempts = 0;
return _deliver(wormhole, internalParams);
} }
// TODO: WIP // TODO: WIP
@ -160,53 +160,58 @@ contract CoreRelayer is CoreRelayerGovernance {
// cache wormhole instance // cache wormhole instance
IWormhole wormhole = wormhole(); IWormhole wormhole = wormhole();
// build InternalDeliveryParams struct to reduce local variable count
InternalDeliveryParams memory internalParams;
// parse the batch // parse the batch
IWormhole.VM2 memory batchVM = wormhole.parseBatchVM(targetParams.encodedVM); internalParams.batchVM = wormhole.parseBatchVM(targetParams.encodedVM);
// cache the deliveryVM // cache the deliveryVM
IWormhole.VM memory deliveryVM = batchVM.indexedObservations[targetParams.deliveryIndex].vm3; IWormhole.VM memory deliveryVM =
parseWormholeObservation(internalParams.batchVM.observations[targetParams.deliveryIndex]);
require(verifyRelayerVM(deliveryVM), "invalid emitter"); require(verifyRelayerVM(deliveryVM), "invalid emitter");
// create the VAAId for the delivery VAA // create the AllowedEmitterSequence for the delivery VAA
VAAId memory deliveryId = VAAId({emitterAddress: deliveryVM.emitterAddress, sequence: deliveryVM.sequence}); internalParams.deliveryId =
AllowedEmitterSequence({emitterAddress: deliveryVM.emitterAddress, sequence: deliveryVM.sequence});
// parse the deliveryVM payload into the DeliveryInstructions struct // parse the deliveryVM payload into the DeliveryInstructions struct
DeliveryInstructions memory deliveryInstructions = decodeDeliveryInstructions(deliveryVM.payload); internalParams.deliveryInstructions = decodeDeliveryInstructions(deliveryVM.payload);
// parse and verify the encoded redelivery message
(IWormhole.VM memory redeliveryVm, bool valid, string memory reason) = (IWormhole.VM memory redeliveryVm, bool valid, string memory reason) =
wormhole.parseAndVerifyVM(encodedRedeliveryVm); wormhole.parseAndVerifyVM(encodedRedeliveryVm);
require(valid, reason); require(valid, reason);
require(verifyRelayerVM(redeliveryVm), "invalid emitter"); require(verifyRelayerVM(redeliveryVm), "invalid emitter");
// parse the RedeliveryInstructions
RedeliveryInstructions memory redeliveryInstructions = parseRedeliveryInstructions(redeliveryVm.payload); RedeliveryInstructions memory redeliveryInstructions = parseRedeliveryInstructions(redeliveryVm.payload);
require(redeliveryInstructions.batchHash == batchVM.hash, "invalid batch"); require(redeliveryInstructions.batchHash == internalParams.batchVM.hash, "invalid batch");
require(redeliveryInstructions.emitterAddress == deliveryVM.emitterAddress, "invalid delivery"); require(
require(redeliveryInstructions.sequence == deliveryVM.sequence, "invalid delivery"); redeliveryInstructions.emitterAddress == internalParams.deliveryId.emitterAddress,
"invalid delivery emitter"
);
require(redeliveryInstructions.sequence == internalParams.deliveryId.sequence, "invalid delivery sequence");
// overwrite the DeliveryInstruction's relayParams // override the DeliveryInstruction's relayParams
deliveryInstructions.relayParameters = redeliveryInstructions.relayParameters; internalParams.deliveryInstructions.relayParameters = redeliveryInstructions.relayParameters;
// parse the new relayParams // parse the new relayParams
RelayParameters memory relayParams = decodeRelayParameters(redeliveryInstructions.relayParameters); internalParams.relayParams = decodeRelayParameters(redeliveryInstructions.relayParameters);
// Overwrite the target gas if requested by the relayer // override the target gas if requested by the relayer
if (targetParams.targetCallGasOverride > relayParams.deliveryGasLimit) { if (targetParams.targetCallGasOverride > internalParams.relayParams.deliveryGasLimit) {
relayParams.deliveryGasLimit = targetParams.targetCallGasOverride; internalParams.relayParams.deliveryGasLimit = targetParams.targetCallGasOverride;
} }
return _deliver( // set the remaining values in the InternalDeliveryParams struct
wormhole, batchVM, deliveryInstructions, deliveryId, relayParams, redeliveryInstructions.deliveryCount internalParams.deliveryIndex = targetParams.deliveryIndex;
); internalParams.deliveryAttempts = redeliveryInstructions.deliveryCount;
return _deliver(wormhole, internalParams);
} }
function _deliver( function _deliver(IWormhole wormhole, InternalDeliveryParams memory internalParams)
IWormhole wormhole,
IWormhole.VM2 memory batchVM,
DeliveryInstructions memory deliveryInstructions,
VAAId memory deliveryId,
RelayParameters memory relayParams,
uint16 attempt
)
internal internal
returns (uint64 sequence) returns (uint64 sequence)
{ {
@ -214,51 +219,50 @@ contract CoreRelayer is CoreRelayerGovernance {
// Compute the hash(batchHash, deliveryId) and check to see if the batch // Compute the hash(batchHash, deliveryId) and check to see if the batch
// was successfully delivered already. Revert if it was. // was successfully delivered already. Revert if it was.
bytes32 deliveryHash = keccak256(abi.encodePacked(batchVM.hash, deliveryId.emitterAddress, deliveryId.sequence)); bytes32 deliveryHash = keccak256(
abi.encodePacked(
internalParams.batchVM.hash,
internalParams.deliveryId.emitterAddress,
internalParams.deliveryId.sequence
)
);
require(!isDeliveryCompleted(deliveryHash), "batch already delivered"); require(!isDeliveryCompleted(deliveryHash), "batch already delivered");
// confirm this is the correct destination chain // confirm this is the correct destination chain
require(chainId() == deliveryInstructions.targetChain, "targetChain is not this chain"); require(chainId() == internalParams.deliveryInstructions.targetChain, "targetChain is not this chain");
// confirm the correct delivery attempt sequence // confirm the correct delivery attempt sequence
uint256 attemptedDeliveryCount = attemptedDeliveryCount(deliveryHash); uint256 attemptedDeliveryCount = attemptedDeliveryCount(deliveryHash);
require(attempt == attemptedDeliveryCount, "wrong delivery attempt index"); require(internalParams.deliveryAttempts == attemptedDeliveryCount, "wrong delivery attempt index");
// Check to see if a deliveryList is specified. If not, confirm that all VAAs made it to this contract.
// If a deliveryList is specified, forward the list of VAAs to the receiving contract.
IWormhole.VM[] memory targetVMs;
{
// bypass stack-too-deep
uint256 deliveryListLength = deliveryInstructions.deliveryList.length;
if (deliveryListLength > 0) {
targetVMs =
preparePartialBatchForDelivery(batchVM.indexedObservations, deliveryInstructions.deliveryList);
} else {
targetVMs =
prepareBatchForDelivery(batchVM.indexedObservations, relayParams.maximumBatchSize, deliveryId);
}
}
// verify the batchVM before calling the receiver // verify the batchVM before calling the receiver
(bool valid, string memory reason) = wormhole.verifyBatchVM(batchVM, true); (bool valid, string memory reason) = wormhole.verifyBatchVM(internalParams.batchVM, true);
require(valid, reason); require(valid, reason);
// remove the deliveryVM from the array of observations in the batch
uint256 numObservations = internalParams.batchVM.observations.length;
bytes[] memory targetObservations = new bytes[](numObservations - 1);
uint256 lastIndex = 0;
for (uint256 i = 0; i < numObservations;) {
if (i != internalParams.deliveryIndex) {
targetObservations[lastIndex] = internalParams.batchVM.observations[i];
unchecked {
lastIndex += 1;
}
}
unchecked {
i += 1;
}
}
// lock the contract to prevent reentrancy // lock the contract to prevent reentrancy
require(!isContractLocked(), "reentrant call"); require(!isContractLocked(), "reentrant call");
setContractLock(true); setContractLock(true);
// process the delivery by calling the wormholeReceiver endpoint on the target contract // call the receiveWormholeMessages endpoint on the target contract
(bool success,) = address(uint160(uint256(deliveryInstructions.targetAddress))).call{ (bool success,) = address(uint160(uint256(internalParams.deliveryInstructions.targetAddress))).call{
gas: relayParams.deliveryGasLimit gas: internalParams.relayParams.deliveryGasLimit
}( }(abi.encodeWithSignature("receiveWormholeMessages(bytes[])", targetObservations));
abi.encodeWithSignature(
"wormholeReceiver((uint8,uint32,uint32,uint16,bytes32,uint64,uint8,bytes,uint32,(bytes32,bytes32,uint8,uint8)[],bytes32)[],uint16,bytes32,bytes)",
targetVMs,
deliveryInstructions.fromChain,
deliveryInstructions.fromAddress,
deliveryInstructions.payload
)
);
// unlock the contract // unlock the contract
setContractLock(false); setContractLock(false);
@ -275,17 +279,19 @@ contract CoreRelayer is CoreRelayerGovernance {
} }
// increment the relayer rewards // increment the relayer rewards
incrementRelayerRewards(msg.sender, deliveryInstructions.fromChain, relayParams.nativePayment); incrementRelayerRewards(
msg.sender, internalParams.deliveryInstructions.fromChain, internalParams.relayParams.nativePayment
);
// clear the cache to reduce gas overhead // clear the cache to reduce gas overhead
wormhole.clearBatchCache(batchVM.hashes); wormhole.clearBatchCache(internalParams.batchVM.hashes);
// emit delivery status message // emit delivery status message
DeliveryStatus memory status = DeliveryStatus({ DeliveryStatus memory status = DeliveryStatus({
payloadID: 2, payloadID: 2,
batchHash: batchVM.hash, batchHash: internalParams.batchVM.hash,
emitterAddress: deliveryId.emitterAddress, emitterAddress: internalParams.deliveryId.emitterAddress,
sequence: deliveryId.sequence, sequence: internalParams.deliveryId.sequence,
deliveryCount: uint16(attemptedDeliveryCount + 1), deliveryCount: uint16(attemptedDeliveryCount + 1),
deliverySuccess: success deliverySuccess: success
}); });
@ -294,7 +300,7 @@ contract CoreRelayer is CoreRelayerGovernance {
} }
// TODO: WIP // TODO: WIP
function rewardPayout(uint16 rewardChain, bytes32 receiver, uint32 nonce) function collectRewards(uint16 rewardChain, bytes32 receiver, uint32 nonce)
public public
payable payable
returns (uint64 sequence) returns (uint64 sequence)
@ -321,7 +327,7 @@ contract CoreRelayer is CoreRelayerGovernance {
} }
// TODO: WIP // TODO: WIP
function finaliseRewardPayout(bytes memory encodedVm) public { function payRewards(bytes memory encodedVm) public {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm); (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
require(valid, reason); require(valid, reason);
@ -334,80 +340,6 @@ contract CoreRelayer is CoreRelayerGovernance {
payable(address(uint160(uint256(payout.receiver)))).transfer(payout.amount); payable(address(uint160(uint256(payout.receiver)))).transfer(payout.amount);
} }
function prepareBatchForDelivery(
IWormhole.IndexedObservation[] memory indexedObservations,
uint8 maximumBatchSize,
VAAId memory deliveryId
)
internal
pure
returns (IWormhole.VM[] memory batch)
{
// array that will hold the resulting VAAs
batch = new IWormhole.VM[](maximumBatchSize);
uint8 observationCount = 0;
uint256 observationsLen = indexedObservations.length;
for (uint256 i = 0; i < observationsLen;) {
// parse the VM
IWormhole.VM memory vm = indexedObservations[i].vm3;
// make sure not to include any deliveryVMs
if (vm.emitterAddress != deliveryId.emitterAddress) {
batch[i] = vm;
observationCount += 1;
}
unchecked {
i += 1;
}
}
// confirm that the whole batch was sent
require(observationCount == maximumBatchSize, "invalid batch size");
}
function preparePartialBatchForDelivery(
IWormhole.IndexedObservation[] memory indexedObservations,
VAAId[] memory deliveryList
)
internal
pure
returns (IWormhole.VM[] memory partialBatch)
{
// cache deliveryList length
uint256 deliveryListLen = deliveryList.length;
// final array with the individual VMs
partialBatch = new IWormhole.VM[](deliveryListLen);
// cache observationsLen to save on gas
uint256 observationsLen = indexedObservations.length;
// loop through the delivery list and save VAAs if they are included in the batch
for (uint256 i = 0; i < deliveryListLen;) {
for (uint256 j = 0; j < observationsLen;) {
// parse the VM
IWormhole.VM memory vm = indexedObservations[j].vm3;
// save if there is a match
if (vm.emitterAddress == deliveryList[i].emitterAddress && vm.sequence == deliveryList[i].sequence) {
partialBatch[i] = vm;
break;
}
unchecked {
j += 1;
}
}
unchecked {
i += 1;
}
}
// confirm that the whole batch was sent
require(partialBatch.length == deliveryListLen, "invalid batch size");
}
function verifyRelayerVM(IWormhole.VM memory vm) internal view returns (bool) { function verifyRelayerVM(IWormhole.VM memory vm) internal view returns (bool) {
if (registeredRelayer(vm.emitterChainId) == vm.emitterAddress) { if (registeredRelayer(vm.emitterChainId) == vm.emitterAddress) {
return true; return true;
@ -415,4 +347,8 @@ contract CoreRelayer is CoreRelayerGovernance {
return false; return false;
} }
function parseWormholeObservation(bytes memory observation) public view returns (IWormhole.VM memory) {
return wormhole().parseVM(observation);
}
} }

View File

@ -11,16 +11,6 @@ import "./CoreRelayerStructs.sol";
contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters { contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
using BytesLib for bytes; using BytesLib for bytes;
function encodeDeliveryList(VAAId[] memory deliveryList) internal pure returns (bytes memory encoded) {
uint256 len = deliveryList.length;
for (uint8 i = 0; i < len;) {
encoded = abi.encodePacked(encoded, deliveryList[i].emitterAddress, deliveryList[i].sequence);
unchecked {
i += 1;
}
}
}
function encodeDeliveryInstructions(DeliveryParameters memory instructions) function encodeDeliveryInstructions(DeliveryParameters memory instructions)
internal internal
view view
@ -32,12 +22,6 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
chainId(), chainId(),
instructions.targetAddress, instructions.targetAddress,
instructions.targetChain, instructions.targetChain,
uint16(instructions.payload.length),
instructions.payload,
uint16(instructions.chainPayload.length),
instructions.chainPayload,
uint16(instructions.deliveryList.length),
encodeDeliveryList(instructions.deliveryList),
uint16(instructions.relayParameters.length), uint16(instructions.relayParameters.length),
instructions.relayParameters instructions.relayParameters
); );
@ -104,40 +88,6 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
instructions.targetChain = encoded.toUint16(index); instructions.targetChain = encoded.toUint16(index);
index += 2; index += 2;
// length of payload
uint16 payloadLen = encoded.toUint16(index);
index += 2;
// payload
instructions.payload = encoded.slice(index, payloadLen);
index += payloadLen;
// length of chain payload
uint16 chainPayloadLen = encoded.toUint16(index);
index += 2;
// chain payload
instructions.chainPayload = encoded.slice(index, chainPayloadLen);
index += chainPayloadLen;
// length of the deliveryList
uint16 deliveryListLen = encoded.toUint16(index);
index += 2;
// list of VAAs to deliver
instructions.deliveryList = new VAAId[](deliveryListLen);
for (uint16 i = 0; i < deliveryListLen;) {
instructions.deliveryList[i].emitterAddress = encoded.toBytes32(index);
index += 32;
instructions.deliveryList[i].sequence = encoded.toUint64(index);
index += 8;
unchecked {
i += 1;
}
}
// length of relayParameters // length of relayParameters
uint16 relayParametersLen = encoded.toUint16(index); uint16 relayParametersLen = encoded.toUint16(index);
index += 2; index += 2;
@ -162,10 +112,6 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
relayParams.deliveryGasLimit = encoded.toUint32(index); relayParams.deliveryGasLimit = encoded.toUint32(index);
index += 4; index += 4;
// maximum batch size
relayParams.maximumBatchSize = encoded.toUint8(index);
index += 1;
// payment made on the source chain // payment made on the source chain
relayParams.nativePayment = encoded.toUint256(index); relayParams.nativePayment = encoded.toUint256(index);
index += 32; index += 32;

View File

@ -3,11 +3,13 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import "../interfaces/IWormhole.sol";
contract CoreRelayerStructs { contract CoreRelayerStructs {
struct VAAId { struct AllowedEmitterSequence {
// VAA emitter address // wormhole emitter address
bytes32 emitterAddress; bytes32 emitterAddress;
// VAA sequence // wormhole message sequence
uint64 sequence; uint64 sequence;
} }
@ -23,10 +25,7 @@ contract CoreRelayerStructs {
struct DeliveryParameters { struct DeliveryParameters {
uint16 targetChain; uint16 targetChain;
bytes32 targetAddress; bytes32 targetAddress;
bytes payload;
VAAId[] deliveryList;
bytes relayParameters; bytes relayParameters;
bytes chainPayload;
uint32 nonce; uint32 nonce;
uint8 consistencyLevel; uint8 consistencyLevel;
} }
@ -37,12 +36,18 @@ contract CoreRelayerStructs {
uint16 fromChain; uint16 fromChain;
bytes32 targetAddress; bytes32 targetAddress;
uint16 targetChain; uint16 targetChain;
bytes payload;
bytes chainPayload;
VAAId[] deliveryList;
bytes relayParameters; bytes relayParameters;
} }
struct InternalDeliveryParams {
IWormhole.VM2 batchVM;
DeliveryInstructions deliveryInstructions;
AllowedEmitterSequence deliveryId;
RelayParameters relayParams;
uint8 deliveryIndex;
uint16 deliveryAttempts;
}
// TODO: WIP // TODO: WIP
struct RedeliveryInstructions { struct RedeliveryInstructions {
uint8 payloadID; // payloadID = 3; uint8 payloadID; // payloadID = 3;
@ -51,7 +56,7 @@ contract CoreRelayerStructs {
// Point to the original delivery instruction // Point to the original delivery instruction
bytes32 emitterAddress; bytes32 emitterAddress;
uint64 sequence; uint64 sequence;
// Current deliverycount // Current number of delivery attempts
uint16 deliveryCount; uint16 deliveryCount;
// New Relayer-Specific Parameters // New Relayer-Specific Parameters
bytes relayParameters; bytes relayParameters;
@ -71,8 +76,6 @@ contract CoreRelayerStructs {
uint8 version; uint8 version;
// gasLimit to call the receiving contract with // gasLimit to call the receiving contract with
uint32 deliveryGasLimit; uint32 deliveryGasLimit;
// maximum batch size
uint8 maximumBatchSize;
// the payment made on the source chain, which is later paid to the relayer // the payment made on the source chain, which is later paid to the relayer
uint256 nativePayment; uint256 nativePayment;
} }

View File

@ -4,7 +4,7 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
interface ICoreRelayer { interface ICoreRelayer {
struct VAAId { struct AllowedEmitterSequence {
// VAA emitter address // VAA emitter address
bytes32 emitterAddress; bytes32 emitterAddress;
// VAA sequence // VAA sequence
@ -23,10 +23,7 @@ interface ICoreRelayer {
struct DeliveryParameters { struct DeliveryParameters {
uint16 targetChain; uint16 targetChain;
bytes32 targetAddress; bytes32 targetAddress;
bytes payload;
VAAId[] deliveryList;
bytes relayParameters; bytes relayParameters;
bytes chainPayload;
uint32 nonce; uint32 nonce;
uint8 consistencyLevel; uint8 consistencyLevel;
} }
@ -37,9 +34,6 @@ interface ICoreRelayer {
uint16 fromChain; uint16 fromChain;
bytes32 targetAddress; bytes32 targetAddress;
uint16 targetChain; uint16 targetChain;
bytes payload;
bytes chainPayload;
VAAId[] deliveryList;
bytes relayParameters; bytes relayParameters;
} }
@ -59,7 +53,7 @@ interface ICoreRelayer {
bytes32 batchHash; bytes32 batchHash;
} }
function estimateCost(uint16 chainId, uint256 gasLimit) external view returns (uint256 gasEstimate); function estimateEvmCost(uint16 chainId, uint256 gasLimit) external view returns (uint256 gasEstimate);
function send(DeliveryParameters memory deliveryParams) external payable returns (uint64 sequence); function send(DeliveryParameters memory deliveryParams) external payable returns (uint64 sequence);

View File

@ -59,8 +59,8 @@ interface IWormhole {
bytes32[] hashes; bytes32[] hashes;
// Computed batch hash - hash(hash(Observation1), hash(Observation2), ...) // Computed batch hash - hash(hash(Observation1), hash(Observation2), ...)
bytes32 hash; bytes32 hash;
// Array of IndexedObservations // Array of observations with prepended version 3
IndexedObservation[] indexedObservations; bytes[] observations;
} }
event LogMessagePublished( event LogMessagePublished(

View File

@ -19,9 +19,6 @@ contract MockRelayerIntegration {
// deployer of this contract // deployer of this contract
address immutable owner; address immutable owner;
// trusted mock integration contracts
mapping(uint16 => bytes32) trustedSenders;
// map that stores payloads from received VAAs // map that stores payloads from received VAAs
mapping(bytes32 => bytes) verifiedPayloads; mapping(bytes32 => bytes) verifiedPayloads;
@ -32,7 +29,7 @@ contract MockRelayerIntegration {
} }
function estimateRelayCosts(uint16 targetChainId, uint256 targetGasLimit) public view returns (uint256) { function estimateRelayCosts(uint16 targetChainId, uint256 targetGasLimit) public view returns (uint256) {
return relayer.estimateCost(targetChainId, targetGasLimit); return relayer.estimateEvmCost(targetChainId, targetGasLimit);
} }
struct RelayerArgs { struct RelayerArgs {
@ -41,22 +38,56 @@ contract MockRelayerIntegration {
address targetAddress; address targetAddress;
uint32 targetGasLimit; uint32 targetGasLimit;
uint8 consistencyLevel; uint8 consistencyLevel;
uint8[] deliveryListIndices; }
function doStuff(uint32 batchNonce, bytes[] calldata payload, uint8[] calldata consistencyLevel)
public
payable
returns (uint64[] memory sequences)
{
// cache the payload count to save on gas
uint256 numInputPayloads = payload.length;
require(numInputPayloads == consistencyLevel.length, "invalid input parameters");
// Cache the wormhole fee to save on gas costs. Then make sure the user sent
// enough native asset to cover the cost of delivery (plus the cost of generating wormhole messages).
uint256 wormholeFee = wormhole.messageFee();
require(msg.value >= wormholeFee * (numInputPayloads + 1));
// Create an array to store the wormhole message sequences. Add
// a slot for the relay message sequence.
sequences = new uint64[](numInputPayloads + 1);
// send each wormhole message and save the message sequence
uint256 messageIdx = 0;
bytes memory verifyingPayload = abi.encodePacked(wormhole.chainId(), uint8(numInputPayloads));
for (; messageIdx < numInputPayloads;) {
sequences[messageIdx] = wormhole.publishMessage{value: wormholeFee}(
batchNonce, payload[messageIdx], consistencyLevel[messageIdx]
);
verifyingPayload = abi.encodePacked(verifyingPayload, emitterAddress(), sequences[messageIdx]);
unchecked {
messageIdx += 1;
}
}
// Encode app-relevant info regarding the input payloads.
// All we care about is source chain id and number of input payloads.
sequences[messageIdx] = wormhole.publishMessage{value: wormholeFee}(
batchNonce,
verifyingPayload,
1 // consistencyLevel
);
} }
function sendBatchToTargetChain( function sendBatchToTargetChain(
bytes[] calldata payload, bytes[] calldata payload,
uint8[] calldata consistencyLevel, uint8[] calldata consistencyLevel,
RelayerArgs memory relayerArgs RelayerArgs memory relayerArgs
) ) public payable returns (uint64 relayerMessageSequence) {
public uint64[] memory doStuffSequences = doStuff(relayerArgs.nonce, payload, consistencyLevel);
payable uint256 numMessageSequences = doStuffSequences.length;
returns (uint64[] memory messageSequences)
{
// cache the payload count to save on gas
uint256 numPayloads = payload.length;
require(numPayloads == consistencyLevel.length, "invalid input parameters");
// estimate the cost of sending the batch based on the user specified gas limit // estimate the cost of sending the batch based on the user specified gas limit
uint256 gasEstimate = estimateRelayCosts(relayerArgs.targetChainId, relayerArgs.targetGasLimit); uint256 gasEstimate = estimateRelayCosts(relayerArgs.targetChainId, relayerArgs.targetGasLimit);
@ -64,76 +95,78 @@ contract MockRelayerIntegration {
// Cache the wormhole fee to save on gas costs. Then make sure the user sent // Cache the wormhole fee to save on gas costs. Then make sure the user sent
// enough native asset to cover the cost of delivery (plus the cost of generating wormhole messages). // enough native asset to cover the cost of delivery (plus the cost of generating wormhole messages).
uint256 wormholeFee = wormhole.messageFee(); uint256 wormholeFee = wormhole.messageFee();
require(msg.value >= gasEstimate + wormholeFee * numPayloads); require(msg.value >= gasEstimate + wormholeFee * (numMessageSequences + 1));
// Create an array to store the wormhole message sequences. Add
// a slot for the relay message sequence.
messageSequences = new uint64[](numPayloads+1);
// create the deliveryList
uint256 deliveryListLength = relayerArgs.deliveryListIndices.length;
ICoreRelayer.VAAId[] memory deliveryList = new ICoreRelayer.VAAId[](deliveryListLength);
// send each wormhole message and save the message sequence
for (uint256 i = 0; i < numPayloads; i++) {
messageSequences[i] =
wormhole.publishMessage{value: wormholeFee}(relayerArgs.nonce, payload[i], consistencyLevel[i]);
// add to delivery list based on the index (if indices are specified)
for (uint256 j = 0; j < deliveryListLength; j++) {
if (i == relayerArgs.deliveryListIndices[j]) {
deliveryList[j] = ICoreRelayer.VAAId({
emitterAddress: bytes32(uint256(uint160(address(this)))),
sequence: messageSequences[i]
});
}
}
}
// encode the relay parameters // encode the relay parameters
bytes memory relayParameters = bytes memory relayParameters = abi.encodePacked(uint8(1), relayerArgs.targetGasLimit, gasEstimate);
abi.encodePacked(uint8(1), relayerArgs.targetGasLimit, uint8(numPayloads), gasEstimate);
// create the relayer params to call the relayer with // create the relayer params to call the relayer with
ICoreRelayer.DeliveryParameters memory deliveryParams = ICoreRelayer.DeliveryParameters({ ICoreRelayer.DeliveryParameters memory deliveryParams = ICoreRelayer.DeliveryParameters({
targetChain: relayerArgs.targetChainId, targetChain: relayerArgs.targetChainId,
targetAddress: bytes32(uint256(uint160(relayerArgs.targetAddress))), targetAddress: bytes32(uint256(uint160(relayerArgs.targetAddress))),
payload: new bytes(0), relayParameters: relayParameters, // REVIEW: rename to encodedRelayParameters?
deliveryList: deliveryList,
relayParameters: relayParameters,
chainPayload: new bytes(0),
nonce: relayerArgs.nonce, nonce: relayerArgs.nonce,
consistencyLevel: relayerArgs.consistencyLevel consistencyLevel: relayerArgs.consistencyLevel
}); });
// call the relayer contract and save the sequence. // call the relayer contract and save the sequence.
messageSequences[numPayloads] = relayer.send{value: gasEstimate}(deliveryParams); relayerMessageSequence = relayer.send{value: gasEstimate}(deliveryParams);
return messageSequences;
} }
function wormholeReceiver( struct EmitterSequence {
IWormhole.VM[] memory vmList, bytes32 emitter;
uint16 sourceChain, uint64 sequence;
bytes32 sourceAddress, }
bytes memory payload
) function parseVerifyingMessage(bytes memory verifyingObservation, uint256 numObservations)
public public
returns (EmitterSequence[] memory emitterSequences)
{ {
// make sure the caller is a trusted relayer contract (IWormhole.VM memory parsed, bool valid, string memory reason) = wormhole.parseAndVerifyVM(verifyingObservation);
require(msg.sender == address(relayer), "caller not trusted"); require(valid, reason);
// make sure the sender of the batch is a trusted contract bytes memory payload = parsed.payload;
require(sourceAddress == trustedSender(sourceChain), "batch sender not trusted"); require(payload.toUint16(0) == parsed.emitterChainId, "source chain != emitterChainId");
require(uint256(payload.toUint8(2)) == numObservations, "incorrect number of observations");
// loop through the array of VMs and store each payload verifiedPayloads[parsed.hash] = payload;
uint256 vmCount = vmList.length;
for (uint256 i = 0; i < vmCount;) { // TODO: instead of returning VM, return a struct that has info to verify observations
(bool valid, string memory reason) = wormhole.verifyVM(vmList[i]); emitterSequences = new EmitterSequence[](numObservations);
uint256 index = 3;
for (uint256 i = 0; i < numObservations;) {
emitterSequences[i].emitter = payload.toBytes32(index);
unchecked {
index += 32;
}
emitterSequences[i].sequence = payload.toUint64(index);
unchecked {
index += 8;
}
unchecked {
i += 1;
}
}
require(payload.length == index, "payload.length != index");
}
function receiveWormholeMessages(bytes[] memory wormholeObservations) public {
// loop through the array of wormhole observations from the batch and store each payload
uint256 numObservations = wormholeObservations.length - 1;
EmitterSequence[] memory emitterSequences =
parseVerifyingMessage(wormholeObservations[numObservations], numObservations);
for (uint256 i = 0; i < numObservations;) {
(IWormhole.VM memory parsed, bool valid, string memory reason) =
wormhole.parseAndVerifyVM(wormholeObservations[i]);
require(valid, reason); require(valid, reason);
// save the payload from each VAA require(emitterSequences[i].emitter == parsed.emitterAddress, "verifying emitter != emitterAddress");
verifiedPayloads[vmList[i].hash] = vmList[i].payload; require(emitterSequences[i].sequence == parsed.sequence, "verifying sequence != sequence");
// save the payload from each wormhole message
verifiedPayloads[parsed.hash] = parsed.payload;
unchecked { unchecked {
i += 1; i += 1;
@ -141,17 +174,6 @@ contract MockRelayerIntegration {
} }
} }
// setters
function registerTrustedSender(uint16 chainId, bytes32 senderAddress) public {
require(msg.sender == owner, "caller must be the owner");
trustedSenders[chainId] = senderAddress;
}
// getters
function trustedSender(uint16 chainId) public view returns (bytes32) {
return trustedSenders[chainId];
}
function getPayload(bytes32 hash) public view returns (bytes memory) { function getPayload(bytes32 hash) public view returns (bytes memory) {
return verifiedPayloads[hash]; return verifiedPayloads[hash];
} }
@ -160,11 +182,15 @@ contract MockRelayerIntegration {
delete verifiedPayloads[hash]; delete verifiedPayloads[hash];
} }
function parseBatchVM(bytes memory encoded) public view returns (IWormhole.VM2 memory) { function parseWormholeBatch(bytes memory encoded) public view returns (IWormhole.VM2 memory) {
return wormhole.parseBatchVM(encoded); return wormhole.parseBatchVM(encoded);
} }
function parseVM(bytes memory encoded) public view returns (IWormhole.VM memory) { function parseWormholeObservation(bytes memory encoded) public view returns (IWormhole.VM memory) {
return wormhole.parseVM(encoded); return wormhole.parseVM(encoded);
} }
function emitterAddress() public view returns (bytes32) {
return bytes32(uint256(uint160(address(this))));
}
} }

View File

@ -34,7 +34,7 @@ contract TestCoreRelayer is CoreRelayer, Test {
struct VMParams { struct VMParams {
uint32 nonce; uint32 nonce;
uint8 consistencyLevel; uint8 consistencyLevel;
uint8 deliveryListCount; uint8 batchCount;
address VMEmitterAddress; address VMEmitterAddress;
address targetAddress; address targetAddress;
} }
@ -83,9 +83,7 @@ contract TestCoreRelayer is CoreRelayer, Test {
address wormhole_, address wormhole_,
uint16 chainId_, uint16 chainId_,
uint32 evmGasOverhead_ uint32 evmGasOverhead_
) ) public {
public
{
vm.assume(chainId_ > 0); vm.assume(chainId_ > 0);
vm.assume(gasOracle_ != address(0)); vm.assume(gasOracle_ != address(0));
vm.assume(wormhole_ != address(0)); vm.assume(wormhole_ != address(0));
@ -125,7 +123,7 @@ contract TestCoreRelayer is CoreRelayer, Test {
gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice); gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice);
// estimate the cost based on the intialized values // estimate the cost based on the intialized values
uint256 gasEstimate = estimateCost(TARGET_CHAIN_ID, gasParams.targetGasLimit); uint256 gasEstimate = estimateEvmCost(TARGET_CHAIN_ID, gasParams.targetGasLimit);
// compute the expected output // compute the expected output
uint256 expectedGasEstimate = ( uint256 expectedGasEstimate = (
@ -196,34 +194,6 @@ contract TestCoreRelayer is CoreRelayer, Test {
assertEq(TARGET_CHAIN_ID, payload.toUint16(index)); assertEq(TARGET_CHAIN_ID, payload.toUint16(index));
index += 2; index += 2;
// payload length
assertEq(deliveryParams.payload.length, payload.toUint16(index));
index += 2;
// payload
assertEq(deliveryParams.payload, payload.slice(index, deliveryParams.payload.length));
index += deliveryParams.payload.length;
// chainPayload length
assertEq(deliveryParams.chainPayload.length, payload.toUint16(index));
index += 2;
// chainPayload
assertEq(deliveryParams.chainPayload, payload.slice(index, deliveryParams.chainPayload.length));
index += deliveryParams.chainPayload.length;
// deliveryList length
assertEq(deliveryParams.deliveryList.length, payload.toUint16(index));
index += 2;
for (uint16 i = 0; i < deliveryParams.deliveryList.length; i++) {
assertEq(deliveryParams.deliveryList[i].emitterAddress, payload.toBytes32(index));
index += 32;
assertEq(deliveryParams.deliveryList[i].sequence, payload.toUint64(index));
index += 8;
}
// relayParameters length // relayParameters length
assertEq(deliveryParams.relayParameters.length, payload.toUint16(index)); assertEq(deliveryParams.relayParameters.length, payload.toUint16(index));
index += 2; index += 2;
@ -237,14 +207,7 @@ contract TestCoreRelayer is CoreRelayer, Test {
// This test confirms that the `send` method generates the correct delivery Instructions payload // This test confirms that the `send` method generates the correct delivery Instructions payload
// to be delivered on the target chain. // to be delivered on the target chain.
function testSend( function testSend(GasParameters memory gasParams, VMParams memory batchParams) public {
GasParameters memory gasParams,
VMParams memory batchParams,
bytes memory relayPayload,
bytes memory chainPayload
)
public
{
uint128 halfMaxUint128 = 2 ** (128 / 2) - 1; uint128 halfMaxUint128 = 2 ** (128 / 2) - 1;
vm.assume(gasParams.evmGasOverhead > 0); vm.assume(gasParams.evmGasOverhead > 0);
vm.assume(gasParams.targetGasLimit > 0); vm.assume(gasParams.targetGasLimit > 0);
@ -255,8 +218,6 @@ contract TestCoreRelayer is CoreRelayer, Test {
vm.assume(batchParams.targetAddress != address(0)); vm.assume(batchParams.targetAddress != address(0));
vm.assume(batchParams.nonce > 0); vm.assume(batchParams.nonce > 0);
vm.assume(batchParams.consistencyLevel > 0); vm.assume(batchParams.consistencyLevel > 0);
vm.assume(relayPayload.length < MAX_UINT16_VALUE);
vm.assume(chainPayload.length < MAX_UINT16_VALUE);
vm.assume(batchParams.VMEmitterAddress != address(0)); vm.assume(batchParams.VMEmitterAddress != address(0));
// initialize all contracts // initialize all contracts
@ -267,24 +228,16 @@ contract TestCoreRelayer is CoreRelayer, Test {
gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice); gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice);
// estimate the cost based on the intialized values // estimate the cost based on the intialized values
uint256 gasEstimate = estimateCost(TARGET_CHAIN_ID, gasParams.targetGasLimit); uint256 gasEstimate = estimateEvmCost(TARGET_CHAIN_ID, gasParams.targetGasLimit);
uint256 wormholeFee = IWormhole(address(wormhole)).messageFee(); uint256 wormholeFee = IWormhole(address(wormhole)).messageFee();
// the balance of this contract is the max Uint96 // the balance of this contract is the max Uint96
vm.assume(gasEstimate < MAX_UINT96_VALUE - wormholeFee); vm.assume(gasEstimate < MAX_UINT96_VALUE - wormholeFee);
// generate a random list of VAAIds for the batch
VAAId[] memory deliveryList = new VAAId[](batchParams.deliveryListCount);
for (uint8 i = 0; i < batchParams.deliveryListCount; i++) {
deliveryList[i] =
VAAId({emitterAddress: bytes32(uint256(uint160(batchParams.VMEmitterAddress))), sequence: uint64(i)});
}
// format the relayParameters // format the relayParameters
bytes memory relayParameters = abi.encodePacked( bytes memory relayParameters = abi.encodePacked(
uint8(1), // version uint8(1), // version
gasParams.targetGasLimit, gasParams.targetGasLimit,
uint8(batchParams.deliveryListCount), // no other VAAs for this test
gasEstimate gasEstimate
); );
@ -292,10 +245,7 @@ contract TestCoreRelayer is CoreRelayer, Test {
DeliveryParameters memory deliveryParams = DeliveryParameters({ DeliveryParameters memory deliveryParams = DeliveryParameters({
targetChain: TARGET_CHAIN_ID, targetChain: TARGET_CHAIN_ID,
targetAddress: bytes32(uint256(uint160(batchParams.targetAddress))), targetAddress: bytes32(uint256(uint160(batchParams.targetAddress))),
payload: relayPayload,
deliveryList: deliveryList,
relayParameters: relayParameters, relayParameters: relayParameters,
chainPayload: chainPayload,
nonce: batchParams.nonce, nonce: batchParams.nonce,
consistencyLevel: batchParams.consistencyLevel consistencyLevel: batchParams.consistencyLevel
}); });
@ -323,12 +273,7 @@ contract TestCoreRelayer is CoreRelayer, Test {
// This tests confirms that the DeliveryInstructions are deserialized correctly // This tests confirms that the DeliveryInstructions are deserialized correctly
// when calling `deliver` on the target chain. // when calling `deliver` on the target chain.
function testDeliveryInstructionDeserialization( function testDeliveryInstructionDeserialization(GasParameters memory gasParams, VMParams memory batchParams)
GasParameters memory gasParams,
VMParams memory batchParams,
bytes memory chainPayload,
bytes memory relayPayload
)
public public
{ {
uint128 halfMaxUint128 = 2 ** (128 / 2) - 1; uint128 halfMaxUint128 = 2 ** (128 / 2) - 1;
@ -341,8 +286,6 @@ contract TestCoreRelayer is CoreRelayer, Test {
vm.assume(batchParams.targetAddress != address(0)); vm.assume(batchParams.targetAddress != address(0));
vm.assume(batchParams.nonce > 0); vm.assume(batchParams.nonce > 0);
vm.assume(batchParams.consistencyLevel > 0); vm.assume(batchParams.consistencyLevel > 0);
vm.assume(relayPayload.length < MAX_UINT16_VALUE);
vm.assume(chainPayload.length < MAX_UINT16_VALUE);
vm.assume(batchParams.VMEmitterAddress != address(0)); vm.assume(batchParams.VMEmitterAddress != address(0));
// initialize all contracts // initialize all contracts
@ -353,24 +296,16 @@ contract TestCoreRelayer is CoreRelayer, Test {
gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice); gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice);
// estimate the cost based on the intialized values // estimate the cost based on the intialized values
uint256 gasEstimate = estimateCost(TARGET_CHAIN_ID, gasParams.targetGasLimit); uint256 gasEstimate = estimateEvmCost(TARGET_CHAIN_ID, gasParams.targetGasLimit);
uint256 wormholeFee = IWormhole(address(wormhole)).messageFee(); uint256 wormholeFee = IWormhole(address(wormhole)).messageFee();
// the balance of this contract is the max Uint96 // the balance of this contract is the max Uint96
vm.assume(gasEstimate < MAX_UINT96_VALUE - wormholeFee); vm.assume(gasEstimate < MAX_UINT96_VALUE - wormholeFee);
// generate a random list of VAAIds for the batch
VAAId[] memory deliveryList = new VAAId[](batchParams.deliveryListCount);
for (uint8 i = 0; i < batchParams.deliveryListCount; i++) {
deliveryList[i] =
VAAId({emitterAddress: bytes32(uint256(uint160(batchParams.VMEmitterAddress))), sequence: uint64(i)});
}
// format the relayParameters // format the relayParameters
bytes memory relayParameters = abi.encodePacked( bytes memory relayParameters = abi.encodePacked(
uint8(1), // version uint8(1), // version
gasParams.targetGasLimit, gasParams.targetGasLimit,
uint8(batchParams.deliveryListCount), // no other VAAs for this test
gasEstimate gasEstimate
); );
@ -378,10 +313,7 @@ contract TestCoreRelayer is CoreRelayer, Test {
DeliveryParameters memory deliveryParams = DeliveryParameters({ DeliveryParameters memory deliveryParams = DeliveryParameters({
targetChain: TARGET_CHAIN_ID, targetChain: TARGET_CHAIN_ID,
targetAddress: bytes32(uint256(uint160(batchParams.targetAddress))), targetAddress: bytes32(uint256(uint160(batchParams.targetAddress))),
payload: relayPayload,
deliveryList: deliveryList,
relayParameters: relayParameters, relayParameters: relayParameters,
chainPayload: chainPayload,
nonce: batchParams.nonce, nonce: batchParams.nonce,
consistencyLevel: batchParams.consistencyLevel consistencyLevel: batchParams.consistencyLevel
}); });
@ -398,15 +330,7 @@ contract TestCoreRelayer is CoreRelayer, Test {
assertEq(SOURCE_CHAIN_ID, instructions.fromChain); assertEq(SOURCE_CHAIN_ID, instructions.fromChain);
assertEq(deliveryParams.targetAddress, instructions.targetAddress); assertEq(deliveryParams.targetAddress, instructions.targetAddress);
assertEq(TARGET_CHAIN_ID, instructions.targetChain); assertEq(TARGET_CHAIN_ID, instructions.targetChain);
assertEq(deliveryParams.payload, instructions.payload);
assertEq(deliveryParams.chainPayload, instructions.chainPayload);
assertEq(deliveryParams.relayParameters, instructions.relayParameters); assertEq(deliveryParams.relayParameters, instructions.relayParameters);
// check the values in the delivery list
for (uint8 i = 0; i < batchParams.deliveryListCount; i++) {
assertEq(deliveryParams.deliveryList[i].emitterAddress, instructions.deliveryList[i].emitterAddress);
assertEq(deliveryParams.deliveryList[i].sequence, instructions.deliveryList[i].sequence);
}
} }
// This tests confirms that the DeliveryInstructions are deserialized correctly // This tests confirms that the DeliveryInstructions are deserialized correctly
@ -429,24 +353,16 @@ contract TestCoreRelayer is CoreRelayer, Test {
gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice); gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice);
// estimate the cost based on the intialized values // estimate the cost based on the intialized values
uint256 gasEstimate = estimateCost(TARGET_CHAIN_ID, gasParams.targetGasLimit); uint256 gasEstimate = estimateEvmCost(TARGET_CHAIN_ID, gasParams.targetGasLimit);
uint256 wormholeFee = IWormhole(address(wormhole)).messageFee(); uint256 wormholeFee = IWormhole(address(wormhole)).messageFee();
// the balance of this contract is the max Uint96 // the balance of this contract is the max Uint96
vm.assume(gasEstimate < MAX_UINT96_VALUE - wormholeFee); vm.assume(gasEstimate < MAX_UINT96_VALUE - wormholeFee);
// generate a random list of VAAIds for the batch
VAAId[] memory deliveryList = new VAAId[](batchParams.deliveryListCount);
for (uint8 i = 0; i < batchParams.deliveryListCount; i++) {
deliveryList[i] =
VAAId({emitterAddress: bytes32(uint256(uint160(batchParams.VMEmitterAddress))), sequence: uint64(i)});
}
// format the relayParameters // format the relayParameters
bytes memory encodedRelayParameters = abi.encodePacked( bytes memory encodedRelayParameters = abi.encodePacked(
uint8(1), // version uint8(1), // version
gasParams.targetGasLimit, gasParams.targetGasLimit,
uint8(batchParams.deliveryListCount), // no other VAAs for this test
gasEstimate gasEstimate
); );
@ -456,7 +372,6 @@ contract TestCoreRelayer is CoreRelayer, Test {
// confirm the values were parsed correctly // confirm the values were parsed correctly
assertEq(uint8(1), decodedRelayParams.version); assertEq(uint8(1), decodedRelayParams.version);
assertEq(gasParams.targetGasLimit, decodedRelayParams.deliveryGasLimit); assertEq(gasParams.targetGasLimit, decodedRelayParams.deliveryGasLimit);
assertEq(uint8(batchParams.deliveryListCount), decodedRelayParams.maximumBatchSize);
assertEq(gasEstimate, decodedRelayParams.nativePayment); assertEq(gasEstimate, decodedRelayParams.nativePayment);
} }
@ -480,24 +395,16 @@ contract TestCoreRelayer is CoreRelayer, Test {
gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice); gasOracle.updatePrice(SOURCE_CHAIN_ID, gasParams.sourceGasPrice, gasParams.sourceNativePrice);
// estimate the cost based on the intialized values // estimate the cost based on the intialized values
uint256 gasEstimate = estimateCost(TARGET_CHAIN_ID, gasParams.targetGasLimit); uint256 gasEstimate = estimateEvmCost(TARGET_CHAIN_ID, gasParams.targetGasLimit);
uint256 wormholeFee = IWormhole(address(wormhole)).messageFee(); uint256 wormholeFee = IWormhole(address(wormhole)).messageFee();
// the balance of this contract is the max Uint96 // the balance of this contract is the max Uint96
vm.assume(gasEstimate < MAX_UINT96_VALUE - wormholeFee); vm.assume(gasEstimate < MAX_UINT96_VALUE - wormholeFee);
// generate a random list of VAAIds for the batch
VAAId[] memory deliveryList = new VAAId[](batchParams.deliveryListCount);
for (uint8 i = 0; i < batchParams.deliveryListCount; i++) {
deliveryList[i] =
VAAId({emitterAddress: bytes32(uint256(uint160(batchParams.VMEmitterAddress))), sequence: uint64(i)});
}
// format the relayParameters (add random bytes to the relayerParams) // format the relayParameters (add random bytes to the relayerParams)
bytes memory encodedRelayParameters = abi.encodePacked( bytes memory encodedRelayParameters = abi.encodePacked(
uint8(1), // version uint8(1), // version
gasParams.targetGasLimit, gasParams.targetGasLimit,
uint8(batchParams.deliveryListCount), // no other VAAs for this test
gasEstimate, gasEstimate,
gasEstimate gasEstimate
); );

View File

@ -1,7 +1,7 @@
import { expect } from "chai"; import {expect} from "chai";
import { ethers } from "ethers"; import {ethers} from "ethers";
import { TargetDeliveryParameters, TestResults } from "./helpers/structs"; import {TargetDeliveryParameters, TestResults} from "./helpers/structs";
import { ChainId, tryNativeToHexString } from "@certusone/wormhole-sdk"; import {ChainId, tryNativeToHexString} from "@certusone/wormhole-sdk";
import { import {
CHAIN_ID_ETH, CHAIN_ID_ETH,
CORE_RELAYER_ADDRESS, CORE_RELAYER_ADDRESS,
@ -9,11 +9,10 @@ import {
RELAYER_DEPLOYER_PRIVATE_KEY, RELAYER_DEPLOYER_PRIVATE_KEY,
MOCK_RELAYER_INTEGRATION_ADDRESS, MOCK_RELAYER_INTEGRATION_ADDRESS,
} from "./helpers/consts"; } from "./helpers/consts";
import { makeContract } from "./helpers/io"; import {makeContract} from "./helpers/io";
import { import {
getSignedBatchVaaFromReceiptOnEth, getSignedBatchVaaFromReceiptOnEth,
getSignedVaaFromReceiptOnEth, getSignedVaaFromReceiptOnEth,
removeObservationFromBatch,
verifyDeliveryStatusPayload, verifyDeliveryStatusPayload,
} from "./helpers/utils"; } from "./helpers/utils";
@ -72,20 +71,6 @@ describe("Core Relayer Integration Test", () => {
expect(actualRegisteredRelayer).to.equal(expectedRegisteredRelayer); expect(actualRegisteredRelayer).to.equal(expectedRegisteredRelayer);
}); });
it("Register trusted senders in the mock integration contract", async () => {
// create hex address for the mock contract
const targetMockContractAddressBytes = "0x" + tryNativeToHexString(mockContract.address, TARGET_CHAIN_ID);
// register the trusted sender with the mock integration contract
await mockContract
.registerTrustedSender(TARGET_CHAIN_ID, targetMockContractAddressBytes)
.then((tx: ethers.ContractTransaction) => tx.wait());
// query the mock integration contract to confirm the trusted sender registration worked
const trustedSender = await mockContract.trustedSender(TARGET_CHAIN_ID);
expect(trustedSender).to.equal(targetMockContractAddressBytes);
});
it("Should update EVM deliver gas overhead", async () => { it("Should update EVM deliver gas overhead", async () => {
// the new evmGasOverhead value // the new evmGasOverhead value
const newEvmGasOverhead = 500000; const newEvmGasOverhead = 500000;
@ -104,7 +89,7 @@ describe("Core Relayer Integration Test", () => {
it("Should create a batch VAA with a DeliveryInstructions VAA", async () => { it("Should create a batch VAA with a DeliveryInstructions VAA", async () => {
// estimate the cost of submitting the batch on the target chain // estimate the cost of submitting the batch on the target chain
fullBatchTest.targetChainGasEstimate = await coreRelayer.estimateCost(TARGET_CHAIN_ID, TARGET_GAS_LIMIT); fullBatchTest.targetChainGasEstimate = await coreRelayer.estimateEvmCost(TARGET_CHAIN_ID, TARGET_GAS_LIMIT);
// relayer args // relayer args
fullBatchTest.relayerArgs = { fullBatchTest.relayerArgs = {
@ -113,7 +98,6 @@ describe("Core Relayer Integration Test", () => {
targetAddress: TARGET_CONTRACT_ADDRESS, targetAddress: TARGET_CONTRACT_ADDRESS,
targetGasLimit: TARGET_GAS_LIMIT, targetGasLimit: TARGET_GAS_LIMIT,
consistencyLevel: deliveryVAAConsistencyLevel, consistencyLevel: deliveryVAAConsistencyLevel,
deliveryListIndices: [] as number[],
}; };
// call the mock integration contract to create a batch // call the mock integration contract to create a batch
@ -133,19 +117,28 @@ describe("Core Relayer Integration Test", () => {
it("Should deserialize and validate the full batch DeliveryInstructions VAA values", async () => { it("Should deserialize and validate the full batch DeliveryInstructions VAA values", async () => {
// parse the batchVM and verify the values // parse the batchVM and verify the values
const parsedBatchVM = await mockContract.parseBatchVM(fullBatchTest.signedBatchVM); const parsedBatchVM = await mockContract.parseWormholeBatch(fullBatchTest.signedBatchVM);
// validate the individual messages // validate the individual messages
const batchLen = parsedBatchVM.indexedObservations.length; const observations = parsedBatchVM.observations;
for (let i = 0; i < batchLen - 1; i++) { const batchLen = parsedBatchVM.observations.length;
const parsedVM = await parsedBatchVM.indexedObservations[i].vm3; for (let i = 0; i < batchLen - 2; ++i) {
const parsedVM = await mockContract.parseWormholeObservation(observations[i]);
expect(parsedVM.nonce).to.equal(batchNonce); expect(parsedVM.nonce).to.equal(batchNonce);
expect(parsedVM.consistencyLevel).to.equal(batchVAAConsistencyLevels[i]); expect(parsedVM.consistencyLevel).to.equal(batchVAAConsistencyLevels[i]);
expect(parsedVM.payload).to.equal(batchVAAPayloads[i]); expect(parsedVM.payload).to.equal(batchVAAPayloads[i]);
} }
// validate the mock integration instructions
const integratorMessage = await mockContract.parseWormholeObservation(observations[batchLen - 2]);
expect(integratorMessage.nonce).to.equal(batchNonce);
expect(integratorMessage.consistencyLevel).to.equal(1);
const integratorMessagePayload = Buffer.from(ethers.utils.arrayify(integratorMessage.payload));
expect(integratorMessagePayload.readUInt16BE(0)).to.equal(2);
expect(integratorMessagePayload.readUInt8(2)).to.equal(batchLen - 2);
// validate the delivery instructions VAA // validate the delivery instructions VAA
const deliveryVM = parsedBatchVM.indexedObservations[batchLen - 1].vm3; const deliveryVM = await mockContract.parseWormholeObservation(observations[batchLen - 1]);
expect(deliveryVM.nonce).to.equal(batchNonce); expect(deliveryVM.nonce).to.equal(batchNonce);
expect(deliveryVM.consistencyLevel).to.equal(fullBatchTest.relayerArgs.consistencyLevel); expect(deliveryVM.consistencyLevel).to.equal(fullBatchTest.relayerArgs.consistencyLevel);
@ -160,15 +153,11 @@ describe("Core Relayer Integration Test", () => {
"0x" + tryNativeToHexString(TARGET_CONTRACT_ADDRESS, CHAIN_ID_ETH) "0x" + tryNativeToHexString(TARGET_CONTRACT_ADDRESS, CHAIN_ID_ETH)
); );
expect(deliveryInstructions.targetChain).to.equal(TARGET_CHAIN_ID); expect(deliveryInstructions.targetChain).to.equal(TARGET_CHAIN_ID);
expect(deliveryInstructions.payload).to.equal("0x");
expect(deliveryInstructions.chainPayload).to.equal("0x");
expect(deliveryInstructions.deliveryList.length).to.equal(0);
// deserialize the deliveryParameters and confirm the values // deserialize the deliveryParameters and confirm the values
const relayParameters = await coreRelayer.decodeRelayParameters(deliveryInstructions.relayParameters); const relayParameters = await coreRelayer.decodeRelayParameters(deliveryInstructions.relayParameters);
expect(relayParameters.version).to.equal(1); expect(relayParameters.version).to.equal(1);
expect(relayParameters.deliveryGasLimit).to.equal(TARGET_GAS_LIMIT); expect(relayParameters.deliveryGasLimit).to.equal(TARGET_GAS_LIMIT);
expect(relayParameters.maximumBatchSize).to.equal(batchVAAPayloads.length);
expect(relayParameters.nativePayment.toString()).to.equal(fullBatchTest.targetChainGasEstimate.toString()); expect(relayParameters.nativePayment.toString()).to.equal(fullBatchTest.targetChainGasEstimate.toString());
}); });
@ -176,7 +165,7 @@ describe("Core Relayer Integration Test", () => {
// create the TargetDeliveryParameters // create the TargetDeliveryParameters
const targetDeliveryParams: TargetDeliveryParameters = { const targetDeliveryParams: TargetDeliveryParameters = {
encodedVM: fullBatchTest.signedBatchVM, encodedVM: fullBatchTest.signedBatchVM,
deliveryIndex: batchVAAPayloads.length, deliveryIndex: batchVAAPayloads.length + 1,
targetCallGasOverride: ethers.BigNumber.from(TARGET_GAS_LIMIT), targetCallGasOverride: ethers.BigNumber.from(TARGET_GAS_LIMIT),
}; };
@ -186,10 +175,12 @@ describe("Core Relayer Integration Test", () => {
.then((tx: ethers.ContractTransaction) => tx.wait()); .then((tx: ethers.ContractTransaction) => tx.wait());
// confirm that the batch VAA payloads were stored in a map in the mock contract // confirm that the batch VAA payloads were stored in a map in the mock contract
const parsedBatchVM = await mockContract.parseBatchVM(fullBatchTest.signedBatchVM); const parsedBatchVM = await mockContract.parseWormholeBatch(fullBatchTest.signedBatchVM);
const batchLen = parsedBatchVM.indexedObservations.length;
for (let i = 0; i < batchLen - 1; i++) { const observations = parsedBatchVM.observations;
const parsedVM = parsedBatchVM.indexedObservations[i].vm3; const batchLen = observations.length;
for (let i = 0; i < batchLen - 1; ++i) {
const parsedVM = await mockContract.parseWormholeObservation(observations[i]);
// query the contract for the saved payload // query the contract for the saved payload
const verifiedPayload = await mockContract.getPayload(parsedVM.hash); const verifiedPayload = await mockContract.getPayload(parsedVM.hash);
@ -213,15 +204,14 @@ describe("Core Relayer Integration Test", () => {
it("Should correctly emit a DeliveryStatus message upon full batch delivery", async () => { it("Should correctly emit a DeliveryStatus message upon full batch delivery", async () => {
// parse the delivery status VAA payload // parse the delivery status VAA payload
const parsedDeliveryStatus = await mockContract.parseVM(fullBatchTest.deliveryStatusVM); const parsedDeliveryStatus = await mockContract.parseWormholeObservation(fullBatchTest.deliveryStatusVM);
const deliveryStatusPayload = parsedDeliveryStatus.payload; const deliveryStatusPayload = parsedDeliveryStatus.payload;
// parse the batch VAA (need to use the batch hash) // parse the batch VAA (need to use the batch hash)
const parsedBatchVM = await mockContract.parseBatchVM(fullBatchTest.signedBatchVM); const parsedBatchVM = await mockContract.parseWormholeBatch(fullBatchTest.signedBatchVM);
// grab the deliveryVM index, which is the last VM in the batch // grab the deliveryVM index, which is the last VM in the batch
const deliveryVMIndex = parsedBatchVM.indexedObservations.length - 1; const deliveryVM = await mockContract.parseWormholeObservation(parsedBatchVM.observations.at(-1)!);
const deliveryVM = parsedBatchVM.indexedObservations[deliveryVMIndex].vm3;
// expected values in the DeliveryStatus payload // expected values in the DeliveryStatus payload
const expectedDeliveryAttempts = 1; const expectedDeliveryAttempts = 1;
@ -243,198 +233,5 @@ describe("Core Relayer Integration Test", () => {
const queriedRelayerFees = await coreRelayer.relayerRewards(wallet.address, TARGET_CHAIN_ID); const queriedRelayerFees = await coreRelayer.relayerRewards(wallet.address, TARGET_CHAIN_ID);
expect(queriedRelayerFees.toString()).to.equal(fullBatchTest.targetChainGasEstimate.toString()); expect(queriedRelayerFees.toString()).to.equal(fullBatchTest.targetChainGasEstimate.toString());
}); });
it("Should create a batch VAA with a DeliveryInstructions VAA (with a VAAId deliveryList)", async () => {
// estimate the gas of submitting the partial batch on the target chain
partialBatchTest.targetChainGasEstimate = await coreRelayer.estimateCost(TARGET_CHAIN_ID, TARGET_GAS_LIMIT);
// randomly select four indices to put in the delivery list (not including the delivery VAA)
let deliveryListIndices: number[] = [];
for (let i = 0; i < batchVAAPayloads.length; i++) {
deliveryListIndices.push(i);
}
deliveryListIndices = deliveryListIndices.sort(() => 0.5 - Math.random()).slice(4);
// relayer args
partialBatchTest.relayerArgs = {
nonce: batchNonce,
targetChainId: TARGET_CHAIN_ID,
targetAddress: TARGET_CONTRACT_ADDRESS,
targetGasLimit: TARGET_GAS_LIMIT,
consistencyLevel: deliveryVAAConsistencyLevel,
deliveryListIndices: deliveryListIndices,
};
// call the mock integration contract to create a batch
const sendReceipt: ethers.ContractReceipt = await mockContract
.sendBatchToTargetChain(batchVAAPayloads, batchVAAConsistencyLevels, partialBatchTest.relayerArgs, {
value: partialBatchTest.targetChainGasEstimate,
})
.then((tx: ethers.ContractTransaction) => tx.wait());
// fetch the signedBatchVAA
partialBatchTest.signedBatchVM = await getSignedBatchVaaFromReceiptOnEth(
sendReceipt,
SOURCE_CHAIN_ID as ChainId,
0 // guardianSetIndex
);
});
it("Should deserialize and validate the partial batch DeliveryInstructions VAA values", async () => {
// parse the batchVM and verify the values
const parsedBatchVM = await mockContract.parseBatchVM(partialBatchTest.signedBatchVM);
// validate the individual messages
const batchLen = parsedBatchVM.indexedObservations.length;
for (let i = 0; i < batchLen - 1; i++) {
const parsedVM = await parsedBatchVM.indexedObservations[i].vm3;
expect(parsedVM.nonce).to.equal(batchNonce);
expect(parsedVM.consistencyLevel).to.equal(batchVAAConsistencyLevels[i]);
expect(parsedVM.payload).to.equal(batchVAAPayloads[i]);
}
// validate the delivery instructions VAA
const deliveryVM = parsedBatchVM.indexedObservations[batchLen - 1].vm3;
expect(deliveryVM.nonce).to.equal(batchNonce);
expect(deliveryVM.consistencyLevel).to.equal(partialBatchTest.relayerArgs.consistencyLevel);
// deserialize the delivery instruction payload and validate the values
const deliveryInstructions = await coreRelayer.decodeDeliveryInstructions(deliveryVM.payload);
expect(deliveryInstructions.payloadID).to.equal(1);
expect(deliveryInstructions.fromAddress).to.equal(
"0x" + tryNativeToHexString(SOURCE_CONTRACT_ADDRESS, CHAIN_ID_ETH)
);
expect(deliveryInstructions.fromChain).to.equal(SOURCE_CHAIN_ID);
expect(deliveryInstructions.targetAddress).to.equal(
"0x" + tryNativeToHexString(TARGET_CONTRACT_ADDRESS, CHAIN_ID_ETH)
);
expect(deliveryInstructions.targetChain).to.equal(TARGET_CHAIN_ID);
expect(deliveryInstructions.payload).to.equal("0x");
expect(deliveryInstructions.chainPayload).to.equal("0x");
expect(deliveryInstructions.deliveryList.length).to.equal(4);
// deserialize the deliveryParameters and confirm the values
const relayParameters = await coreRelayer.decodeRelayParameters(deliveryInstructions.relayParameters);
expect(relayParameters.version).to.equal(1);
expect(relayParameters.deliveryGasLimit).to.equal(TARGET_GAS_LIMIT);
expect(relayParameters.maximumBatchSize).to.equal(batchVAAPayloads.length);
expect(relayParameters.nativePayment.toString()).to.equal(partialBatchTest.targetChainGasEstimate.toString());
});
it("Should create a partial batch based on the deliveryList in the DeliveryInstructions VAA", async () => {
// parse the batchVM and deserialize the DeliveryInstructions
const parsedBatchVM = await mockContract.parseBatchVM(partialBatchTest.signedBatchVM);
// Delivery VAA starting index (before pruning the batch). It should be the
// last VAA in the batch.
const deliveryVAAIndex: number = batchVAAPayloads.length;
// The delivery VAA is the last message in the batch. Relayers will not know this,
// and will have to iterate through the batch to find the VAAId.
const deliveryInstructionsPayload = await coreRelayer.decodeDeliveryInstructions(
parsedBatchVM.indexedObservations[deliveryVAAIndex].vm3.payload
);
// Loop through the deliveryList in the DeliveryInstructions and find the indices to deliver. Store
// the delivery index to make sure that it is not removed from the batch.
let indicesToKeep: number[] = [deliveryVAAIndex];
for (const deliveryId of deliveryInstructionsPayload.deliveryList) {
for (const indexedObservations of parsedBatchVM.indexedObservations) {
let vm3 = indexedObservations.vm3;
if (
vm3.emitterAddress == deliveryId.emitterAddress &&
vm3.sequence.toString() == deliveryId.sequence.toString()
) {
indicesToKeep.push(indexedObservations.index);
}
}
}
// prune the batch
for (const indexedObservations of parsedBatchVM.indexedObservations) {
const index = indexedObservations.index;
if (!indicesToKeep.includes(index)) {
// prune the batch
partialBatchTest.signedBatchVM = removeObservationFromBatch(index, partialBatchTest.signedBatchVM);
}
}
// confirm that the indices that we care about are still in the VAA
const prunedBatchVM = await mockContract.parseBatchVM(partialBatchTest.signedBatchVM);
for (const indexedObservations of prunedBatchVM.indexedObservations) {
expect(indicesToKeep.includes(indexedObservations.index)).to.be.true;
}
});
it("Should deliver the partial batch VAA and call the wormholeReceiver endpoint on the mock contract", async () => {
// The delivery VAA index has changed since the batch was pruned, find the new delivery VAA index.
// It should still be the last VAA in the batch.
const prunedBatchVM = await mockContract.parseBatchVM(partialBatchTest.signedBatchVM);
const deliveryVAAIndex = prunedBatchVM.indexedObservations.length - 1;
expect(prunedBatchVM.indexedObservations[deliveryVAAIndex].vm3.emitterAddress).to.equal(RELAYER_EMITTER_ADDRESS);
// create the TargetDeliveryParameters
const targetDeliveryParams: TargetDeliveryParameters = {
encodedVM: partialBatchTest.signedBatchVM,
deliveryIndex: deliveryVAAIndex,
targetCallGasOverride: ethers.BigNumber.from(TARGET_GAS_LIMIT),
};
// call the deliver method on the relayer contract
const deliveryReceipt: ethers.ContractReceipt = await coreRelayer
.deliver(targetDeliveryParams)
.then((tx: ethers.ContractTransaction) => tx.wait());
// confirm that the batch VM payloads were stored in a map in the mock contract
const batchLen = prunedBatchVM.indexedObservations.length;
for (let i = 0; i < batchLen - 1; i++) {
const parsedVM = prunedBatchVM.indexedObservations[i].vm3;
// query the contract for the saved payload
const verifiedPayload = await mockContract.getPayload(parsedVM.hash);
expect(verifiedPayload).to.equal(parsedVM.payload);
// clear the payload from storage for future tests
await mockContract.clearPayload(parsedVM.hash).then((tx: ethers.ContractTransaction) => tx.wait());
// confirm that the payload was cleared
const emptyPayload = await mockContract.getPayload(parsedVM.hash);
expect(emptyPayload).to.equal("0x");
}
// fetch and save the delivery status VAA
partialBatchTest.deliveryStatusVM = await getSignedVaaFromReceiptOnEth(
deliveryReceipt,
TARGET_CHAIN_ID,
0 // guardianSetIndex
);
});
it("Should correctly emit a DeliveryStatus message upon partial batch delivery", async () => {
// parse the VM payload
const parsedDeliveryStatus = await mockContract.parseVM(partialBatchTest.deliveryStatusVM);
const deliveryStatusPayload = parsedDeliveryStatus.payload;
// parse the batch VAA (need to use the batch hash)
const parsedBatchVM = await mockContract.parseBatchVM(partialBatchTest.signedBatchVM);
// grab the deliveryVM based, which is the last VM in the batch
const deliveryVMIndex = parsedBatchVM.indexedObservations.length - 1;
const deliveryVM = parsedBatchVM.indexedObservations[deliveryVMIndex].vm3;
// expected values in the DeliveryStatus payload
const expectedDeliveryAttempts = 1;
const expectedSuccessBoolean = 1;
const success = verifyDeliveryStatusPayload(
deliveryStatusPayload,
parsedBatchVM.hash,
RELAYER_EMITTER_ADDRESS,
deliveryVM.sequence,
expectedDeliveryAttempts,
expectedSuccessBoolean
);
expect(success).to.be.true;
});
}); });
}); });

View File

@ -6,7 +6,6 @@ export interface RelayerArgs {
targetAddress: string; targetAddress: string;
targetGasLimit: number; targetGasLimit: number;
consistencyLevel: number; consistencyLevel: number;
deliveryListIndices: number[];
} }
export interface TargetDeliveryParameters { export interface TargetDeliveryParameters {

View File

@ -1,6 +1,6 @@
import {ethers} from "ethers"; import { ethers } from "ethers";
import {ChainId, tryNativeToHexString} from "@certusone/wormhole-sdk"; import { ChainId, tryNativeToHexString } from "@certusone/wormhole-sdk";
import {WORMHOLE_MESSAGE_EVENT_ABI, GUARDIAN_PRIVATE_KEY} from "./consts"; import { WORMHOLE_MESSAGE_EVENT_ABI, GUARDIAN_PRIVATE_KEY } from "./consts";
const elliptic = require("elliptic"); const elliptic = require("elliptic");
export async function parseWormholeEventsFromReceipt( export async function parseWormholeEventsFromReceipt(
@ -84,7 +84,7 @@ export async function getSignedBatchVaaFromReceiptOnEth(
// sign the batchHash // sign the batchHash
const ec = new elliptic.ec("secp256k1"); const ec = new elliptic.ec("secp256k1");
const key = ec.keyFromPrivate(GUARDIAN_PRIVATE_KEY); const key = ec.keyFromPrivate(GUARDIAN_PRIVATE_KEY);
const signature = key.sign(batchHash.substring(2), {canonical: true}); const signature = key.sign(batchHash.substring(2), { canonical: true });
// create the signature // create the signature
const packSig = [ const packSig = [
@ -153,7 +153,7 @@ export async function getSignedVaaFromReceiptOnEth(
// sign the batchHash // sign the batchHash
const ec = new elliptic.ec("secp256k1"); const ec = new elliptic.ec("secp256k1");
const key = ec.keyFromPrivate(GUARDIAN_PRIVATE_KEY); const key = ec.keyFromPrivate(GUARDIAN_PRIVATE_KEY);
const signature = key.sign(hash.substring(2), {canonical: true}); const signature = key.sign(hash.substring(2), { canonical: true });
// create the signature // create the signature
const packSig = [ const packSig = [
@ -275,10 +275,10 @@ export function verifyDeliveryStatusPayload(
console.log("Invalid batch hash"); console.log("Invalid batch hash");
return false; return false;
} else if (emitterAddress != relayerAddress) { } else if (emitterAddress != relayerAddress) {
console.log("Invalid emitter address in delivery VAAId"); console.log("Invalid emitter address in delivery AllowedEmitterSequenceedEmitterSequence");
return false; return false;
} else if (sequence != deliverySequence) { } else if (sequence != deliverySequence) {
console.log("Invalid emitter address in delivery VAAId"); console.log("Invalid emitter address in delivery AllowedEmitterSequenceedEmitterSequence");
return false; return false;
} else if (deliveryCount != deliveryAttempts) { } else if (deliveryCount != deliveryAttempts) {
console.log("Invalid number of delivery attempts"); console.log("Invalid number of delivery attempts");

2
offchain-relayer/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.relayer.yaml
relay/ethereum/core_relayer/abi.go

View File

@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -exuo pipefail set -exuo pipefail
cat relayer.tilt.yaml > .relayer.yaml
# function for updating or inserting a KEY: value pair in a file. # function for updating or inserting a KEY: value pair in a file.
function upsert_env_file { function upsert_env_file {

View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -exuo pipefail
cd $(dirname $0)/..
jq '.abi' ./ethereum/build/CoreRelayer.sol/CoreRelayer.json | docker run -i --rm -v $(pwd):/root -u $(id -u):$(id -g) ethereum/client-go:alltools-stable abigen --abi - --pkg core_relayer --type CoreRelayer --out /root/offchain-relayer/relay/ethereum/core_relayer/abi.go

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,10 @@
evmRPC: ws://localhost:8545 evmRPC: ws://localhost:8545
evmContract: 0x2f3efA6bbDC5fAf4dC1a600765c7B7829e47bE10 evmContract: 0xdeadbeef
evmWormholeChainID: 2 evmWormholeChainID: 2
evmNetworkID: 1 evmNetworkID: 1
evm2RPC: ws://localhost:8546 evm2RPC: ws://localhost:8546
evm2Contract: 0x2f3efA6bbDC5fAf4dC1a600765c7B7829e47bE10 evm2Contract: 0xdeadbeef
evm2WormholeChainID: 4 evm2WormholeChainID: 4
evm2NetworkID: 56 evm2NetworkID: 56

View File

@ -10,19 +10,19 @@ import {
ZERO_ADDRESS_BYTES, ZERO_ADDRESS_BYTES,
TARGET_GAS_LIMIT, TARGET_GAS_LIMIT,
} from "./helpers/consts"; } from "./helpers/consts";
import {RelayerArgs } from "./helpers/structs"; import { RelayerArgs } from "./helpers/structs";
import { import {
makeCoreRelayerFromForgeBroadcast, makeCoreRelayerFromForgeBroadcast,
makeGasOracleFromForgeBroadcast, makeGasOracleFromForgeBroadcast,
makeMockRelayerIntegrationFromForgeBroadcast, makeMockRelayerIntegrationFromForgeBroadcast,
resolvePath resolvePath,
} from "./helpers/utils"; } from "./helpers/utils";
import { import {
CHAIN_ID_BSC, CHAIN_ID_BSC,
CHAIN_ID_ETH, CHAIN_ID_ETH,
getSignedBatchVAAWithRetry, getSignedBatchVAAWithRetry,
tryNativeToUint8Array, tryNativeToUint8Array,
tryNativeToHexString tryNativeToHexString,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport"; import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
@ -113,24 +113,6 @@ describe("ETH <> BSC Generic Relayer Integration Test", () => {
.registerChain(CHAIN_ID_BSC, tryNativeToUint8Array(bscCoreRelayer.address, CHAIN_ID_BSC)) .registerChain(CHAIN_ID_BSC, tryNativeToUint8Array(bscCoreRelayer.address, CHAIN_ID_BSC))
.then((tx) => tx.wait()); .then((tx) => tx.wait());
} }
// Query the mock relayer integration contracts to see if trusted mock relayer
// integration contracts have been registered.
const trustedSenderOnBsc = await bscRelayerIntegrator.trustedSender(CHAIN_ID_ETH);
const trustedSenderOnEth = await ethRelayerIntegrator.trustedSender(CHAIN_ID_BSC);
// register the trusted mock relayer integration contracts
if (trustedSenderOnBsc == ZERO_ADDRESS_BYTES) {
await bscRelayerIntegrator
.registerTrustedSender(CHAIN_ID_ETH, tryNativeToUint8Array(ethRelayerIntegrator.address, CHAIN_ID_ETH))
.then((tx) => tx.wait());
}
if (trustedSenderOnEth == ZERO_ADDRESS_BYTES) {
await ethRelayerIntegrator
.registerTrustedSender(CHAIN_ID_BSC, tryNativeToUint8Array(bscRelayerIntegrator.address, CHAIN_ID_BSC))
.then((tx) => tx.wait());
}
}); });
describe("Send from Ethereum and Deliver to BSC", () => { describe("Send from Ethereum and Deliver to BSC", () => {
@ -178,7 +160,6 @@ describe("ETH <> BSC Generic Relayer Integration Test", () => {
targetAddress: bscRelayerIntegrator.address, targetAddress: bscRelayerIntegrator.address,
targetGasLimit: TARGET_GAS_LIMIT, targetGasLimit: TARGET_GAS_LIMIT,
consistencyLevel: batchVaaConsistencyLevels[0], consistencyLevel: batchVaaConsistencyLevels[0],
deliveryListIndices: [] as number[], // no indices specified for full batch delivery
}; };
// call the mock integration contract and send the batch VAA // call the mock integration contract and send the batch VAA
@ -204,12 +185,12 @@ describe("ETH <> BSC Generic Relayer Integration Test", () => {
it("Wait for off-chain relayer to deliver the batch VAA to BSC", async () => { it("Wait for off-chain relayer to deliver the batch VAA to BSC", async () => {
// parse the batch VAA // parse the batch VAA
const parsedBatch = await ethRelayerIntegrator.parseBatchVM(batchVaaFromEth); const parsedBatch = await ethRelayerIntegrator.parseWormholeBatch(batchVaaFromEth);
// Check to see if the batch VAA was delivered by querying the contract // Check to see if the batch VAA was delivered by querying the contract
// for the first payload sent in the batch. // for the first payload sent in the batch.
let isBatchDelivered: boolean = false; let isBatchDelivered: boolean = false;
const targetVm3 = parsedBatch.indexedObservations[0].vm3; const targetVm3 = await ethRelayerIntegrator.parseWormholeObservation(parsedBatch.observations[0]);
while (!isBatchDelivered) { while (!isBatchDelivered) {
// query the contract to see if the batch was delivered // query the contract to see if the batch was delivered
const storedPayload = await bscRelayerIntegrator.getPayload(targetVm3.hash); const storedPayload = await bscRelayerIntegrator.getPayload(targetVm3.hash);
@ -219,11 +200,11 @@ describe("ETH <> BSC Generic Relayer Integration Test", () => {
} }
// confirm that the remaining payloads are stored in the contract // confirm that the remaining payloads are stored in the contract
for (const indexedObservation of parsedBatch.indexedObservations) { for (const observation of parsedBatch.observations) {
const vm3 = indexedObservation.vm3; const vm3 = await bscRelayerIntegrator.parseWormholeObservation(observation);
// skip delivery instructions VM // skip delivery instructions VM
if (vm3.emitterAddress == "0x"+tryNativeToHexString(ethCoreRelayer.address, CHAIN_ID_ETH)) { if (vm3.emitterAddress == "0x" + tryNativeToHexString(ethCoreRelayer.address, CHAIN_ID_ETH)) {
continue; continue;
} }

View File

@ -1,4 +1,4 @@
import {ethers} from "ethers"; import { ethers } from "ethers";
export interface RelayerArgs { export interface RelayerArgs {
nonce: number; nonce: number;
@ -6,7 +6,6 @@ export interface RelayerArgs {
targetAddress: string; targetAddress: string;
targetGasLimit: number; targetGasLimit: number;
consistencyLevel: number; consistencyLevel: number;
deliveryListIndices: number[];
} }
export interface TargetDeliveryParameters { export interface TargetDeliveryParameters {