-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Entity clarification #35
Changes from 10 commits
17a5022
ad6f536
59168b1
c852bf0
8aa7580
5f379c4
4e44e22
6fc4c95
1ed1e87
b095508
4f1d1a1
b35122f
cbefd16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,6 +90,30 @@ impl TryFrom<EthereumConfig> for Provider<Http> { | |
} | ||
} | ||
|
||
pub fn get_multicall_data( | ||
target_contracts: Vec<Address>, | ||
target_calldata: Vec<Bytes>, | ||
bid_amounts: Vec<BidAmount>, | ||
) -> Result<Vec<MulticallData>> { | ||
if (target_contracts.len() != target_calldata.len()) | ||
|| (target_contracts.len() != bid_amounts.len()) | ||
{ | ||
return Err(anyhow!( | ||
"target_contracts, target_calldata, and bid_amounts must have the same length" | ||
)); | ||
} | ||
|
||
let mut multicall_data = vec![]; | ||
for i in 0..target_contracts.len() { | ||
multicall_data.push(MulticallData { | ||
target_contract: target_contracts[i], | ||
target_calldata: target_calldata[i].clone(), | ||
bid_amount: bid_amounts[i], | ||
}); | ||
} | ||
Ok(multicall_data) | ||
} | ||
|
||
pub fn get_simulation_call( | ||
relayer: Address, | ||
provider: Provider<Http>, | ||
|
@@ -98,25 +122,26 @@ pub fn get_simulation_call( | |
target_contracts: Vec<Address>, | ||
target_calldata: Vec<Bytes>, | ||
bid_amounts: Vec<BidAmount>, | ||
) -> FunctionCall<Arc<Provider<Http>>, Provider<Http>, Vec<MulticallStatus>> { | ||
) -> Result<FunctionCall<Arc<Provider<Http>>, Provider<Http>, Vec<MulticallStatus>>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we can group these 3 arguments everywhere, the error where the length of these 3 vectors do not match will not exist in the first place. And |
||
let client = Arc::new(provider); | ||
let express_relay_contract = | ||
ExpressRelayContract::new(chain_config.express_relay_contract, client); | ||
|
||
express_relay_contract | ||
.multicall( | ||
permission_key, | ||
target_contracts, | ||
target_calldata, | ||
bid_amounts, | ||
) | ||
.from(relayer) | ||
let multicall_data_result = get_multicall_data(target_contracts, target_calldata, bid_amounts); | ||
|
||
match multicall_data_result { | ||
Ok(multicall_data) => Ok(express_relay_contract | ||
.multicall(permission_key, multicall_data) | ||
.from(relayer)), | ||
Err(e) => Err(e), | ||
} | ||
} | ||
|
||
|
||
pub enum SimulationError { | ||
LogicalError { result: Bytes, reason: String }, | ||
ContractError(ContractError<Provider<Http>>), | ||
InputError { reason: String }, | ||
} | ||
|
||
|
||
|
@@ -139,7 +164,7 @@ pub async fn simulate_bids( | |
target_calldata: Vec<Bytes>, | ||
bid_amounts: Vec<BidAmount>, | ||
) -> Result<(), SimulationError> { | ||
let call = get_simulation_call( | ||
let call_result = get_simulation_call( | ||
relayer, | ||
provider, | ||
chain_config, | ||
|
@@ -148,21 +173,29 @@ pub async fn simulate_bids( | |
target_calldata, | ||
bid_amounts, | ||
); | ||
match call.await { | ||
Ok(results) => { | ||
evaluate_simulation_results(results)?; | ||
} | ||
Err(e) => { | ||
return Err(SimulationError::ContractError(e)); | ||
match call_result { | ||
Ok(call) => { | ||
match call.await { | ||
Ok(results) => { | ||
evaluate_simulation_results(results)?; | ||
} | ||
Err(e) => { | ||
return Err(SimulationError::ContractError(e)); | ||
} | ||
}; | ||
Ok(()) | ||
} | ||
}; | ||
Ok(()) | ||
Err(e) => Err(SimulationError::InputError { | ||
reason: e.to_string(), | ||
}), | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub enum SubmissionError { | ||
ProviderError(ProviderError), | ||
ContractError(ContractError<SignableProvider>), | ||
InputError { reason: String }, | ||
} | ||
|
||
/// Transformer that converts a transaction into a legacy transaction if use_legacy_tx is true. | ||
|
@@ -203,12 +236,12 @@ pub async fn submit_bids( | |
|
||
let express_relay_contract = | ||
SignableExpressRelayContract::new(chain_config.express_relay_contract, client); | ||
let call = express_relay_contract.multicall( | ||
permission, | ||
target_contracts, | ||
target_calldata, | ||
bid_amounts, | ||
); | ||
|
||
let multicall_data = get_multicall_data(target_contracts, target_calldata, bid_amounts) | ||
.map_err(|e| SubmissionError::InputError { | ||
reason: e.to_string(), | ||
})?; | ||
let call = express_relay_contract.multicall(permission, multicall_data); | ||
let mut gas_estimate = call | ||
.estimate_gas() | ||
.await | ||
|
@@ -343,6 +376,7 @@ pub async fn handle_bid(store: Arc<Store>, bid: Bid) -> result::Result<Uuid, Res | |
ContractError::ProviderError { e: _ } => Err(RestError::TemporarilyUnavailable), | ||
_ => Err(RestError::BadParameters(format!("Error: {}", e))), | ||
}, | ||
SimulationError::InputError { reason } => Err(RestError::BadParameters(reason)), | ||
}; | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,110 +3,73 @@ pragma solidity ^0.8.13; | |
|
||
import "./Errors.sol"; | ||
import "./Structs.sol"; | ||
import "./ExpressRelayState.sol"; | ||
import "./ExpressRelayHelpers.sol"; | ||
|
||
import "openzeppelin-contracts/contracts/utils/Strings.sol"; | ||
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; | ||
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelay.sol"; | ||
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelayFeeReceiver.sol"; | ||
|
||
contract ExpressRelay is IExpressRelay { | ||
contract ExpressRelay is ExpressRelayHelpers, ExpressRelayState { | ||
event ReceivedETH(address sender, uint256 amount); | ||
|
||
// TODO: separate the notion operator into relayer and admin. | ||
// TODO: Relayer can submit transactions, admin can change relayers and set fees | ||
address _operator; | ||
mapping(address => uint256) _feeConfig; | ||
mapping(bytes32 => bool) _permissions; | ||
uint256 _defaultFee; | ||
|
||
/** | ||
* @notice ExpressRelay constructor - Initializes a new multicall contract with given parameters | ||
* | ||
* @param operator: address of express relay operator EOA | ||
* @param defaultFee: default fee split to be paid to the protocol whose permissioning is being used | ||
*/ | ||
constructor(address operator, uint256 defaultFee) { | ||
_operator = operator; | ||
_defaultFee = defaultFee; | ||
} | ||
|
||
/** | ||
* @notice getOperator function - returns the address of the express relay operator | ||
*/ | ||
function getOperator() public view returns (address) { | ||
return _operator; | ||
} | ||
|
||
function isPermissioned( | ||
address protocolFeeReceiver, | ||
bytes calldata permissionId | ||
) public view returns (bool permissioned) { | ||
return | ||
_permissions[ | ||
keccak256(abi.encode(protocolFeeReceiver, permissionId)) | ||
]; | ||
} | ||
|
||
/** | ||
* @notice setFee function - sets the fee for a given fee recipient | ||
* @notice ExpressRelay constructor - Initializes a new ExpressRelay contract with given parameters | ||
* | ||
* @param feeRecipient: address of the fee recipient for the contract being registered | ||
* @param feeSplit: amount of fee to be split with the protocol. 10**18 is 100% | ||
* @param admin: address of admin of express relay | ||
* @param relayer: address of relayer EOA | ||
* @param feeSplitProtocolDefault: default fee split to be paid to the protocol whose permissioning is being used | ||
* @param feeSplitRelayer: split of the non-protocol fees to be paid to the relayer | ||
*/ | ||
function setFee(address feeRecipient, uint256 feeSplit) public { | ||
if (msg.sender != _operator) { | ||
revert Unauthorized(); | ||
constructor( | ||
address admin, | ||
address relayer, | ||
uint256 feeSplitProtocolDefault, | ||
uint256 feeSplitRelayer | ||
) { | ||
state.admin = admin; | ||
state.relayer = relayer; | ||
|
||
if (feeSplitProtocolDefault > state.feeSplitPrecision) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this checking logic can become a separate function |
||
revert InvalidFeeSplit(); | ||
} | ||
_feeConfig[feeRecipient] = feeSplit; | ||
} | ||
state.feeSplitProtocolDefault = feeSplitProtocolDefault; | ||
|
||
function _isContract(address _addr) private view returns (bool) { | ||
uint32 size; | ||
assembly { | ||
size := extcodesize(_addr) | ||
if (feeSplitRelayer > state.feeSplitPrecision) { | ||
revert InvalidFeeSplit(); | ||
} | ||
return (size > 0); | ||
} | ||
|
||
function _bytesToAddress( | ||
bytes memory bys | ||
) private pure returns (address addr) { | ||
// this does not assume the struct fields of the permission key | ||
addr = address(uint160(uint256(bytes32(bys)))); | ||
state.feeSplitRelayer = feeSplitRelayer; | ||
} | ||
|
||
/** | ||
* @notice multicall function - performs a number of calls to external contracts in order | ||
* | ||
* @param permissionKey: permission to allow for this call | ||
* @param targetContracts: ordered list of contracts to call into | ||
* @param targetCalldata: ordered list of calldata to call the targets with | ||
* @param bidAmounts: ordered list of bids; call i will fail if it does not send this contract at least bid i | ||
* @param multicallData: ordered list of data for multicall, consisting of targetContract, targetCalldata, and bidAmount | ||
*/ | ||
function multicall( | ||
bytes calldata permissionKey, | ||
address[] calldata targetContracts, | ||
bytes[] calldata targetCalldata, | ||
uint256[] calldata bidAmounts | ||
) public payable returns (MulticallStatus[] memory multicallStatuses) { | ||
if (msg.sender != _operator) { | ||
revert Unauthorized(); | ||
} | ||
MulticallData[] calldata multicallData | ||
) | ||
public | ||
payable | ||
onlyRelayer | ||
returns (MulticallStatus[] memory multicallStatuses) | ||
{ | ||
if (permissionKey.length < 20) { | ||
revert InvalidPermission(); | ||
} | ||
|
||
_permissions[keccak256(permissionKey)] = true; | ||
multicallStatuses = new MulticallStatus[](targetCalldata.length); | ||
state.permissions[keccak256(permissionKey)] = true; | ||
multicallStatuses = new MulticallStatus[](multicallData.length); | ||
|
||
uint256 totalBid = 0; | ||
for (uint256 i = 0; i < targetCalldata.length; i++) { | ||
// try/catch will revert if call to searcher fails or if bid conditions not met | ||
for (uint256 i = 0; i < multicallData.length; i++) { | ||
try | ||
// callWithBid will revert if call to external contract fails or if bid conditions not met | ||
this.callWithBid( | ||
targetContracts[i], | ||
targetCalldata[i], | ||
bidAmounts[i] | ||
multicallData[i].targetContract, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's just pass the whole object and do the unpacking on the other side. |
||
multicallData[i].targetCalldata, | ||
multicallData[i].bidAmount | ||
) | ||
returns (bool success, bytes memory result) { | ||
multicallStatuses[i].externalSuccess = success; | ||
|
@@ -117,30 +80,36 @@ contract ExpressRelay is IExpressRelay { | |
|
||
// only count bid if call was successful (and bid was paid out) | ||
if (multicallStatuses[i].externalSuccess) { | ||
totalBid += bidAmounts[i]; | ||
totalBid += multicallData[i].bidAmount; | ||
} | ||
} | ||
|
||
// use the first 20 bytes of permission as fee receiver | ||
address feeReceiver = _bytesToAddress(permissionKey); | ||
address feeReceiver = bytesToAddress(permissionKey); | ||
// transfer fee to the protocol | ||
uint256 protocolFee = _feeConfig[feeReceiver]; | ||
if (protocolFee == 0) { | ||
protocolFee = _defaultFee; | ||
uint256 feeSplitProtocol = state.feeConfig[feeReceiver]; | ||
if (feeSplitProtocol == 0) { | ||
feeSplitProtocol = state.feeSplitProtocolDefault; | ||
} | ||
uint256 feeProtocolNumerator = totalBid * protocolFee; | ||
if (feeProtocolNumerator > 0) { | ||
uint256 feeProtocol = feeProtocolNumerator / | ||
1000_000_000_000_000_000; | ||
if (_isContract(feeReceiver)) { | ||
uint256 feeProtocol = (totalBid * feeSplitProtocol) / | ||
state.feeSplitPrecision; | ||
if (feeProtocol > 0) { | ||
if (isContract(feeReceiver)) { | ||
IExpressRelayFeeReceiver(feeReceiver).receiveAuctionProceedings{ | ||
value: feeProtocol | ||
}(permissionKey); | ||
} else { | ||
payable(feeReceiver).transfer(feeProtocol); | ||
} | ||
} | ||
_permissions[keccak256(permissionKey)] = false; | ||
state.permissions[keccak256(permissionKey)] = false; | ||
|
||
// pay the relayer | ||
uint256 feeRelayer = ((totalBid - feeProtocol) * | ||
state.feeSplitRelayer) / state.feeSplitPrecision; | ||
if (feeRelayer > 0) { | ||
payable(state.relayer).transfer(feeRelayer); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -155,6 +124,11 @@ contract ExpressRelay is IExpressRelay { | |
bytes calldata targetCalldata, | ||
uint256 bid | ||
) public payable returns (bool, bytes memory) { | ||
// manual check for internal call (function is public for try/catch) | ||
if (msg.sender != address(this)) { | ||
revert Unauthorized(); | ||
} | ||
|
||
uint256 balanceInitEth = address(this).balance; | ||
|
||
(bool success, bytes memory result) = targetContract.call( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be great if we can basically do the same thing you did on the contract side on the rust side.
There are many functions having these 3 separate arguments which can be replaced by
Bid
orMulticallData
.