pyth-crosschain/target_chains/ethereum/contracts/forge-test/Executor.t.sol

644 lines
18 KiB
Solidity

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
import "../contracts/executor/ExecutorUpgradable.sol";
import "./utils/WormholeTestUtils.t.sol";
import "./utils/InvalidMagic.t.sol";
contract ExecutorTest is Test, WormholeTestUtils {
Wormhole public wormhole;
ExecutorUpgradable public executor;
ExecutorUpgradable public executor2;
TestCallable public callable;
InvalidMagic public executorInvalidMagic;
uint16 OWNER_CHAIN_ID = 7;
bytes32 OWNER_EMITTER = bytes32(uint256(1));
uint8 NUM_SIGNERS = 1;
function setUp() public {
address _wormhole = setUpWormholeReceiver(NUM_SIGNERS);
ExecutorUpgradable _executor = new ExecutorUpgradable();
ERC1967Proxy _proxy = new ERC1967Proxy(address(_executor), "");
executor = ExecutorUpgradable(payable(address(_proxy)));
executor2 = new ExecutorUpgradable();
executorInvalidMagic = new InvalidMagic();
executor.initialize(
_wormhole,
0,
CHAIN_ID,
OWNER_CHAIN_ID,
OWNER_EMITTER
);
callable = new TestCallable();
}
function testExecute(
address callAddress,
bytes memory callData,
uint64 sequence,
uint value
) internal returns (bytes memory vaa) {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
callAddress,
value,
callData
);
vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
sequence,
payload,
NUM_SIGNERS
);
executor.execute(vaa);
}
function getTestUpgradeVaa(
address newImplementation,
uint value
) internal returns (bytes memory vaa) {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(executor),
value,
abi.encodeWithSelector(
ExecutorUpgradable.upgradeTo.selector,
newImplementation
)
);
vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS
);
}
function testExecutorOwnerChainId() public {
uint chainId = executor.getOwnerChainId();
assertEq(chainId, OWNER_CHAIN_ID);
}
function testExecutorOwnerEmitterAddress() public {
bytes32 ownerEmitterAddress = executor.getOwnerEmitterAddress();
assertEq(ownerEmitterAddress, OWNER_EMITTER);
}
function testExecutorOwner() public {
assertEq(address(executor), executor.owner());
}
function testExternaUpgradeCallFails() public {
vm.expectRevert("Ownable: caller is not the owner");
executor.upgradeTo(address(executor2));
}
function testUpgradeCallSucceedsForContractWithCorrectMagic() public {
bytes memory vaa = getTestUpgradeVaa(address(executor2), 0);
executor.execute(vaa);
}
function testUpgradeCallFailsForNotUUPSContract() public {
bytes memory vaa = getTestUpgradeVaa(address(callable), 0);
vm.expectRevert("ERC1967Upgrade: new implementation is not UUPS");
executor.execute(vaa);
}
function testUpgradeCallFailsForInvalidMagic() public {
bytes memory vaa = getTestUpgradeVaa(address(executorInvalidMagic), 0);
vm.expectRevert(ExecutorErrors.InvalidMagicValue.selector);
executor.execute(vaa);
}
function testLastExecutedSequenceUpdateOnSucceed() public {
callable.reset();
uint32 c = callable.fooCount();
uint oldSequence = executor.getLastExecutedSequence();
uint64 sequence = 1;
assertEq(callable.lastCaller(), address(bytes20(0)));
testExecute(
address(callable),
abi.encodeWithSelector(ICallable.foo.selector),
sequence,
0
);
uint newSequence = executor.getLastExecutedSequence();
assertGt(newSequence, oldSequence);
assertEq(newSequence, sequence);
assertEq(callable.fooCount(), c + 1);
assertEq(callable.lastCaller(), address(executor));
// Sanity check to make sure the check above is meaningful.
assert(address(executor) != address(this));
}
function testLastExecutedSequenceNoChangeOnFail() public {
uint oldSequence = executor.getLastExecutedSequence();
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.reverts.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS
);
vm.expectRevert("call should revert");
executor.execute(vaa);
uint newSequence = executor.getLastExecutedSequence();
assertEq(newSequence, oldSequence);
}
function testCallSucceeds() public {
callable.reset();
uint32 c = callable.fooCount();
assertEq(callable.lastCaller(), address(bytes20(0)));
testExecute(
address(callable),
abi.encodeWithSelector(ICallable.foo.selector),
1,
0
);
assertEq(callable.fooCount(), c + 1);
assertEq(callable.lastCaller(), address(executor));
// Sanity check to make sure the check above is meaningful.
assert(address(executor) != address(this));
}
function testCallWithValueSucceeds() public {
callable.reset();
uint32 c = callable.fooCount();
assertEq(callable.lastCaller(), address(bytes20(0)));
uint value = 1;
vm.deal(address(executor), value);
testExecute(
address(callable),
abi.encodeWithSelector(ICallable.fooPayable.selector),
1,
value
);
assertEq(callable.fooCount(), c + 1);
assertEq(callable.lastCaller(), address(executor));
assertEq(address(executor).balance, 0);
assertEq(address(callable).balance, value);
// Sanity check to make sure the check above is meaningful.
assert(address(executor) != address(this));
}
function testCallWithValueInsufficientBalance() public {
callable.reset();
assertEq(callable.lastCaller(), address(bytes20(0)));
uint value = 5;
vm.deal(address(executor), 1);
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(callable),
value,
abi.encodeWithSelector(ICallable.fooPayable.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS
);
vm.expectRevert();
executor.execute(vaa);
}
function testCallWithArgsSucceeds() public {
callable.reset();
uint32 c = callable.fooCount();
assertEq(callable.lastCaller(), address(bytes20(0)));
testExecute(
address(callable),
abi.encodeWithSelector(ICallable.fooWithArgs.selector, 17),
1,
0
);
assertEq(callable.fooCount(), c + 17);
assertEq(callable.lastCaller(), address(executor));
// Sanity check to make sure the check above is meaningful.
assert(address(executor) != address(this));
}
function testCallWithArgsAndValueSucceeds() public {
callable.reset();
uint32 c = callable.fooCount();
assertEq(callable.lastCaller(), address(bytes20(0)));
uint value = 1;
vm.deal(address(executor), value);
testExecute(
address(callable),
abi.encodeWithSelector(ICallable.fooPayableWithArgs.selector, 17),
1,
value
);
assertEq(callable.fooCount(), c + 17);
assertEq(callable.lastCaller(), address(executor));
assertEq(address(executor).balance, 0);
assertEq(address(callable).balance, value);
// Sanity check to make sure the check above is meaningful.
assert(address(executor) != address(this));
}
function testCallerAddress() public {
uint32 c = callable.fooCount();
testExecute(
address(callable),
abi.encodeWithSelector(ICallable.foo.selector),
1,
0
);
assertEq(callable.fooCount(), c + 1);
}
function testIncorrectVaa() public {
string[5] memory forgeItems = [
"vaaSignature",
"vaaVersion",
"vaaGuardianSetIndex",
"vaaNumSigners+",
"vaaNumSigners-"
];
for (uint i = 0; i < forgeItems.length; i++) {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.foo.selector)
);
bytes memory vaa = forgeVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS,
bytes(forgeItems[i])
);
// ExecutorErrors.InvalidWormholeVaa.selector
vm.expectRevert();
executor.execute(vaa);
}
}
function testIncorrectOwnerEmitterAddress() public {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.foo.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
bytes32(uint256(2)),
1,
payload,
NUM_SIGNERS
);
vm.expectRevert(ExecutorErrors.UnauthorizedEmitter.selector);
executor.execute(vaa);
}
function testIncorrectOwnerEmitterChainId() public {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.foo.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
8,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS
);
vm.expectRevert(ExecutorErrors.UnauthorizedEmitter.selector);
executor.execute(vaa);
}
function testOutOfOrder() public {
testExecute(
address(callable),
abi.encodeWithSelector(ICallable.foo.selector),
3,
0
);
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.foo.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
3,
payload,
NUM_SIGNERS
);
vm.expectRevert(ExecutorErrors.MessageOutOfOrder.selector);
executor.execute(vaa);
callable.reset();
testExecute(
address(callable),
abi.encodeWithSelector(ICallable.foo.selector),
4,
0
);
assertEq(callable.fooCount(), 1);
}
function testInvalidPayload() public {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.foo.selector)
);
bytes memory shortPayload = BytesLib.slice(
payload,
0,
payload.length - 1
);
bytes memory shortVaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
shortPayload,
NUM_SIGNERS
);
vm.expectRevert();
executor.execute(shortVaa);
}
function testIncorrectTargetChainId() public {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
uint16(3),
address(executor),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.foo.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS
);
vm.expectRevert(ExecutorErrors.InvalidGovernanceTarget.selector);
executor.execute(vaa);
}
function testIncorrectTargetAddress() public {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(0x1),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.foo.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS
);
vm.expectRevert(ExecutorErrors.DeserializationError.selector);
executor.execute(vaa);
}
function testIncorrectAction() public {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
uint8(17),
CHAIN_ID,
address(executor),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.foo.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS
);
vm.expectRevert();
executor.execute(vaa);
}
function testCallReverts() public {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(callable),
uint(0),
abi.encodeWithSelector(ICallable.reverts.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS
);
vm.expectRevert("call should revert");
executor.execute(vaa);
}
function testCallToEoaReverts() public {
bytes memory payload = abi.encodePacked(
uint32(0x5054474d),
PythGovernanceInstructions.GovernanceModule.EvmExecutor,
Executor.ExecutorAction.Execute,
CHAIN_ID,
address(executor),
address(100),
uint(0),
abi.encodeWithSelector(ICallable.foo.selector)
);
bytes memory vaa = generateVaa(
uint32(block.timestamp),
OWNER_CHAIN_ID,
OWNER_EMITTER,
1,
payload,
NUM_SIGNERS
);
vm.expectRevert(ExecutorErrors.InvalidContractTarget.selector);
executor.execute(vaa);
}
}
interface ICallable {
function foo() external;
function fooPayable() external payable;
function fooWithArgs(uint32 inc) external;
function fooPayableWithArgs(uint32 inc) external payable;
function reverts() external;
function reset() external;
}
contract TestCallable is ICallable {
uint32 public fooCount = 0;
address public lastCaller = address(bytes20(0));
constructor() {}
function reset() external override {
fooCount = 0;
lastCaller = address(bytes20(0));
}
function foo() external override {
fooCount += 1;
lastCaller = msg.sender;
}
function fooPayable() external payable override {
fooCount += 1;
lastCaller = msg.sender;
}
function fooWithArgs(uint32 inc) external override {
fooCount += inc;
lastCaller = msg.sender;
}
function fooPayableWithArgs(uint32 inc) external payable override {
fooCount += inc;
lastCaller = msg.sender;
}
function reverts() external override {
revert("call should revert");
}
}