644 lines
18 KiB
Solidity
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");
|
|
}
|
|
}
|