| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186 |
5×
1×
4×
4×
1×
3×
3×
1×
1×
3×
3×
3×
3×
3×
2×
1×
3×
3×
4×
4×
2×
1×
1×
| // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Ownable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Ownable.sol';
import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol';
import { IInterchainProposalExecutor } from './interfaces/IInterchainProposalExecutor.sol';
import { InterchainCalls } from './lib/InterchainCalls.sol';
/**
* @title InterchainProposalExecutor
* @dev This contract is intended to be the destination contract for `InterchainProposalSender` contract.
* The proposal will be finally executed from this contract on the destination chain.
*
* The contract maintains whitelists for proposal senders and proposal callers. Proposal senders
* are InterchainProposalSender contracts at the source chain and proposal callers are contracts
* that call the InterchainProposalSender at the source chain.
* For most governance system, the proposal caller should be the Timelock contract.
*
* Some functions need to be implemented in a derived contract.
*/
contract InterchainProposalExecutor is IInterchainProposalExecutor, AxelarExecutable, Ownable {
// Whitelisted proposal callers. The proposal caller is the contract that calls the `InterchainProposalSender` at the source chain.
mapping(string => mapping(bytes => bool)) public whitelistedCallers;
// Whitelisted proposal senders. The proposal sender is the `InterchainProposalSender` contract address at the source chain.
mapping(string => mapping(string => bool)) public whitelistedSenders;
constructor(address _gateway, address _owner) AxelarExecutable(_gateway) Ownable(_owner) {}
/**
* @dev Executes the proposal. The source address must be a whitelisted sender.
* @param sourceAddress The source address
* @param payload The payload. It is ABI encoded of the caller and calls.
* Where:
* - `caller` is the address that calls the `InterchainProposalSender` at the source chain.
* - `calls` is the array of `InterchainCalls.Call` to execute. Each call contains the target, value, signature and data.
*/
function _execute(
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) internal override {
// Check that the source address is whitelisted
if (!whitelistedSenders[sourceChain][sourceAddress]) {
revert NotWhitelistedSourceAddress();
}
// Decode the payload
(bytes memory sourceCaller, InterchainCalls.Call[] memory calls) = abi.decode(
payload,
(bytes, InterchainCalls.Call[])
);
// Check that the caller is whitelisted
if (!whitelistedCallers[sourceChain][sourceCaller]) {
revert NotWhitelistedCaller();
}
_beforeProposalExecuted(sourceChain, sourceAddress, sourceCaller, calls);
// Execute the proposal with the given arguments
_executeProposal(calls);
_onProposalExecuted(sourceChain, sourceAddress, sourceCaller, payload);
emit ProposalExecuted(keccak256(abi.encode(sourceChain, sourceAddress, sourceCaller, payload)));
}
/**
* @dev Executes the proposal. Calls each target with the respective value, signature, and data.
* @param calls The calls to execute.
*/
function _executeProposal(InterchainCalls.Call[] memory calls) internal {
uint256 length = calls.length;
for (uint256 i = 0; i < length; i++) {
InterchainCalls.Call memory call = calls[i];
(bool success, bytes memory result) = call.target.call{ value: call.value }(call.callData);
if (!success) {
_onTargetExecutionFailed(call, result);
} else {
_onTargetExecuted(call, result);
}
}
}
/**
* @dev Set the proposal caller whitelist status
* @param sourceChain The source chain
* @param sourceCaller The source caller
* @param whitelisted The whitelist status
*/
function setWhitelistedProposalCaller(
string calldata sourceChain,
bytes memory sourceCaller,
bool whitelisted
) external override onlyOwner {
whitelistedCallers[sourceChain][sourceCaller] = whitelisted;
emit WhitelistedProposalCallerSet(sourceChain, sourceCaller, whitelisted);
}
/**
* @dev Set the proposal sender whitelist status
* @param sourceChain The source chain
* @param sourceSender The source sender
* @param whitelisted The whitelist status
*/
function setWhitelistedProposalSender(
string calldata sourceChain,
string calldata sourceSender,
bool whitelisted
) external override onlyOwner {
whitelistedSenders[sourceChain][sourceSender] = whitelisted;
emit WhitelistedProposalSenderSet(sourceChain, sourceSender, whitelisted);
}
/**
* @dev Receive native tokens for the proposal that requires native tokens.
*/
receive() external payable {}
/**
* @dev A callback function that is called before the proposal is executed.
* This function can be used to handle the payload before the proposal is executed.
* @param sourceChain The source chain from where the proposal was sent.
* @param sourceAddress The source address that sent the proposal. The source address should be the `InterchainProposalSender` contract address at the source chain.
* @param caller The caller that calls the `InterchainProposalSender` at the source chain.
* @param calls The array of `InterchainCalls.Call` to execute. Each call contains the target, value, and callData.
*/
function _beforeProposalExecuted(
string calldata sourceChain,
string calldata sourceAddress,
bytes memory caller,
InterchainCalls.Call[] memory calls
) internal virtual {
// You can add your own logic here to handle the payload before the proposal is executed.
}
/**
* @dev A callback function that is called after the proposal is executed.
* This function emits an event containing the hash of the payload to signify successful execution.
* @param payload The payload. It is ABI encoded of the caller and calls.
* Where:
* - `caller` is the address that calls the `InterchainProposalSender` at the source chain.
* - `calls` is the array of `InterchainCalls.Call` to execute. Each call contains the target, value, signature and data.
*/
function _onProposalExecuted(
string calldata /* sourceChain */,
string calldata /* sourceAddress */,
bytes memory /* caller */,
bytes calldata payload
) internal virtual {
// You can add your own logic here to handle the payload after the proposal is executed.
}
/**
* @dev A callback function that is called when the execution of a target contract within a proposal fails.
* This function will revert the transaction providing the failure reason if present in the failure data.
* @param result The return data from the failed call to the target contract.
*/
function _onTargetExecutionFailed(InterchainCalls.Call memory /* call */, bytes memory result) internal virtual {
// You can add your own logic here to handle the failure of the target contract execution. The code below is just an example.
if (result.length > 0) {
// The failure data is a revert reason string.
assembly {
revert(add(32, result), mload(result))
}
} else {
// There is no failure data, just revert with no reason.
revert ProposalExecuteFailed();
}
}
/**
* @dev Called after a target is successfully executed. The derived contract should implement this function.
* This function should do some post-execution work, such as emitting events.
* @param call The call that has been executed.
* @param result The result of the call.
*/
function _onTargetExecuted(InterchainCalls.Call memory call, bytes memory result) internal virtual {
// You can add your own logic here to handle the success of each target contract execution.
}
}
|