Skip to content
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

Merged
merged 13 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 58 additions & 24 deletions auction-server/src/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,30 @@ impl TryFrom<EthereumConfig> for Provider<Http> {
}
}

pub fn get_multicall_data(
Copy link
Collaborator

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 or MulticallData.

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>,
Expand All @@ -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>>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 get_simulation_call will not need to return a Result

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 },
}


Expand All @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)),
};
};

Expand Down
2 changes: 1 addition & 1 deletion auction-server/src/opportunity_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ pub async fn verify_opportunity(
vec![chain_store.config.opportunity_adapter_contract],
vec![adapter_calldata],
vec![fake_bid.amount],
)
)?
.tx;
let mut state = spoof::State::default();
let token_spoof_info = chain_store.token_spoof_info.read().await.clone();
Expand Down
12 changes: 11 additions & 1 deletion per_multicall/script/Vault.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,19 @@ contract VaultScript is Script {
);
console.log("pk per operator", operatorAddress);
console.log("sk per operator", operatorSk);
// since feeSplitPrecision is set to 10 ** 18, this represents ~50% of the fees
uint256 feeSplitProtocolDefault = 50 * (10 ** 16);
m30m marked this conversation as resolved.
Show resolved Hide resolved
// ~5% (10% of the remaining 50%) of the fees go to the relayer
uint256 feeSplitRelayer = 10 * (10 ** 16);
vm.startBroadcast(skDeployer);
payable(operatorAddress).transfer(0.01 ether);
ExpressRelay multicall = new ExpressRelay(operatorAddress, 0);
// TODO: set admin to xc-admin
ExpressRelay multicall = new ExpressRelay(
operatorAddress,
operatorAddress,
feeSplitProtocolDefault,
feeSplitRelayer
);
vm.stopBroadcast();
console.log("deployed ExpressRelay contract at", address(multicall));
return address(multicall);
Expand Down
3 changes: 3 additions & 0 deletions per_multicall/src/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ error InvalidSignatureLength();
// The new contract does not have the same magic value as the old one.
// Signature: 0x4ed848c1
error InvalidMagicValue();

// Signature: 0x0601f697
error InvalidFeeSplit();
146 changes: 60 additions & 86 deletions per_multicall/src/ExpressRelay.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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,
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
Expand All @@ -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);
}
}

/**
Expand All @@ -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(
Expand Down
Loading
Loading