Skip to content

Commit

Permalink
feat: Add support for ERC-20 direct token transfers and primary events (
Browse files Browse the repository at this point in the history
#469)

- Extend data structures to handle ERC20 `Transfer` events.
- Introduce primary events that override secondary events within the same transaction.
- Enhance `ValidEvents` enum to be iterable for easier maintenance.
- Set tier-2 receiver address as the bridge contract address padded to 32 bytes.
- Limit secondary event support to the EthereumBridge pallet only.
- New event will only be supported by EthBridge pallet.
- Makes `LogLiftedToPredictionMarket` to use `LiftedData`
  • Loading branch information
thadouk authored Jan 10, 2025
1 parent 0908417 commit 5fca822
Show file tree
Hide file tree
Showing 13 changed files with 112 additions and 55 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions node/avn-service/src/ethereum_events_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use sp_avn_common::{
},
event_types::{
AddedValidatorData, AvtGrowthLiftedData, AvtLowerClaimedData, Error, EthEvent, EthEventId,
EventData, LiftedData, LiftedToPredictionMarketData, NftCancelListingData,
NftEndBatchListingData, NftMintData, NftTransferToData, ValidEvents,
EventData, LiftedData, NftCancelListingData, NftEndBatchListingData, NftMintData,
NftTransferToData, ValidEvents,
},
AVN_KEY_ID,
};
Expand Down Expand Up @@ -143,7 +143,7 @@ impl EventRegistry {
ValidEvents::LiftedToPredictionMarket.signature(),
EventInfo {
parser: |data, topics| {
LiftedToPredictionMarketData::parse_bytes(data, topics)
LiftedData::parse_bytes(data, topics)
.map_err(|err| AppError::ParsingError(err.into()))
.map(|data| EventData::LogLiftedToPredictionMarket(data))
},
Expand Down
4 changes: 2 additions & 2 deletions pallets/eth-bridge/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ fn setup_active_range<T: Config>(partition_index: u16) -> EthBlockRange {
ActiveEthereumRange::<T>::put(ActiveEthRange {
range: range.clone(),
partition: partition_index,
event_types_filter: T::EthereumEventsFilter::get_filter(),
event_types_filter: T::EthereumEventsFilter::get(),
});

range
Expand Down Expand Up @@ -513,7 +513,7 @@ benchmarks! {
length: eth_block_range_size,
},
partition: 0,
event_types_filter: T::EthereumEventsFilter::get_filter(),
event_types_filter: T::EthereumEventsFilter::get(),
};

ensure!(ActiveEthereumRange::<T>::get().is_some(), "Active range not set");
Expand Down
4 changes: 2 additions & 2 deletions pallets/eth-bridge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ pub mod pallet {
ActiveEthereumRange::<T>::put(ActiveEthRange {
range: selected_range,
partition: 0,
event_types_filter: T::EthereumEventsFilter::get_filter(),
event_types_filter: T::EthereumEventsFilter::get(),
});

let _ = SubmittedEthBlocks::<T>::clear(
Expand Down Expand Up @@ -844,7 +844,7 @@ pub mod pallet {
ActiveEthRange {
range: active_range.range.next_range(),
partition: 0,
event_types_filter: T::EthereumEventsFilter::get_filter(),
event_types_filter: T::EthereumEventsFilter::get(),
}
} else {
ActiveEthRange {
Expand Down
18 changes: 9 additions & 9 deletions pallets/ethereum-events/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ use sp_avn_common::{
event_discovery::EthereumEventsFilterTrait,
event_types::{
AddedValidatorData, AvtGrowthLiftedData, AvtLowerClaimedData, Challenge, ChallengeReason,
CheckResult, EthEventCheckResult, EthEventId, EventData, LiftedData,
LiftedToPredictionMarketData, NftCancelListingData, NftEndBatchListingData, NftMintData,
NftTransferToData, ProcessedEventHandler, ValidEvents, Validator,
CheckResult, EthEventCheckResult, EthEventId, EventData, LiftedData, NftCancelListingData,
NftEndBatchListingData, NftMintData, NftTransferToData, ProcessedEventHandler, ValidEvents,
Validator,
},
verify_signature, EthQueryRequest, EthQueryResponse, EthQueryResponseType, EthTransaction,
IngressCounter, InnerCallValidator, Proof,
Expand Down Expand Up @@ -1069,11 +1069,10 @@ impl<T: Config> Pallet<T> {
})?;
return Ok(EventData::LogLifted(event_data))
} else if event_id.signature == ValidEvents::LiftedToPredictionMarket.signature() {
let event_data =
<LiftedToPredictionMarketData>::parse_bytes(data, topics).map_err(|e| {
log::warn!("Error parsing T1 Prediction market lift Event: {:#?}", e);
Error::<T>::EventParsingFailed
})?;
let event_data = <LiftedData>::parse_bytes(data, topics).map_err(|e| {
log::warn!("Error parsing T1 Prediction market lift Event: {:#?}", e);
Error::<T>::EventParsingFailed
})?;
return Ok(EventData::LogLiftedToPredictionMarket(event_data))
} else if event_id.signature == ValidEvents::NftMint.signature() {
let event_data = <NftMintData>::parse_bytes(data, topics).map_err(|e| {
Expand Down Expand Up @@ -1497,8 +1496,9 @@ impl<T: Config> Pallet<T> {

/// Adds an event: tx_hash must be a nonzero hash
fn add_event(event_type: ValidEvents, tx_hash: H256, sender: T::AccountId) -> DispatchResult {
let filter = T::EthereumEventsFilter::get_filter();
let filter = T::EthereumEventsFilter::get_primary();
ensure!(!filter.contains(&event_type), Error::<T>::ErrorAddingEthereumLog);
ensure!(event_type.is_primary(), Error::<T>::InvalidEventToProcess);

let event_id = EthEventId { signature: event_type.signature(), transaction_hash: tx_hash };
ensure!(!Self::event_exists_in_system(&event_id), Error::<T>::DuplicateEvent);
Expand Down
2 changes: 1 addition & 1 deletion pallets/ethereum-events/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ pub type EthBridgeEventsFilter = BoundedBTreeSet<ValidEvents, EventsTypesLimit>;
pub struct MyEthereumEventsFilter;

impl EthereumEventsFilterTrait for MyEthereumEventsFilter {
fn get_filter() -> EthBridgeEventsFilter {
fn get() -> EthBridgeEventsFilter {
let allowed_events: BTreeSet<ValidEvents> =
vec![ValidEvents::AvtLowerClaimed].into_iter().collect();

Expand Down
1 change: 1 addition & 0 deletions pallets/ethereum-events/src/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod test_get_contract_address_for {
ValidEvents::AvtGrowthLifted => H160::from(BRIDGE_CONTRACT),
ValidEvents::AvtLowerClaimed => H160::from(BRIDGE_CONTRACT),
ValidEvents::LiftedToPredictionMarket => H160::from(BRIDGE_CONTRACT),
ValidEvents::Erc20DirectTransfer => H160::from(BRIDGE_CONTRACT),
}
}

Expand Down
1 change: 1 addition & 0 deletions primitives/avn-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ scale-info = { version = "2.9.0", default-features = false, features = [
"derive",
] }
log = { version = "0.4.20", default-features = false }
strum = { version = "*", features = ["derive"], default-features = false }

sp-core = { default-features = false, git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0" }
sp-std = { default-features = false, git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0" }
Expand Down
14 changes: 12 additions & 2 deletions primitives/avn-common/src/event_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,21 @@ pub type EventsTypesLimit = ConstU32<20>;
pub type EthBridgeEventsFilter = BoundedBTreeSet<ValidEvents, EventsTypesLimit>;

pub trait EthereumEventsFilterTrait {
fn get_filter() -> EthBridgeEventsFilter;
/// Returns all events included in the filter.
fn get() -> EthBridgeEventsFilter;

/// Returns only primary events included in the filter.
fn get_primary() -> EthBridgeEventsFilter {
let mut events_filter = Self::get();
for event in ValidEvents::values().iter().filter(|e| !e.is_primary()) {
events_filter.remove(event);
}
events_filter
}
}

impl EthereumEventsFilterTrait for () {
fn get_filter() -> EthBridgeEventsFilter {
fn get() -> EthBridgeEventsFilter {
Default::default()
}
}
Expand Down
110 changes: 77 additions & 33 deletions primitives/avn-common/src/event_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use hex_literal::hex;
use sp_core::{bounded::BoundedVec, H160, H256, H512, U256};
use sp_runtime::{scale_info::TypeInfo, traits::Member, DispatchError, DispatchResult};
use sp_std::{convert::TryInto, vec::Vec};
use strum::{EnumIter, IntoEnumIterator};

// ================================= Events Types ====================================

Expand Down Expand Up @@ -67,17 +68,41 @@ pub enum Error {
LiftedToPredictionMarketEventBadTopicLength,
}

#[derive(Encode, Decode, Clone, PartialOrd, Ord, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
#[derive(
Encode, Decode, Clone, PartialOrd, Ord, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen, EnumIter,
)]

/// Represents the set of valid events supported by the AvN.
///
/// # Overview
/// This enumeration defines the types of events that the system recognizes and processes.
/// The majority of these events are emitted by the bridge contract and are classified as **primary
/// events**. Events that are associated with the contract but emitted by other contracts are
/// classified as **secondary events**.
///
/// Primary events take precedence and override secondary events if both occur in the same
/// transaction.
pub enum ValidEvents {
/// A validator was added.
AddedValidator,
/// A lift operation was executed.
Lifted,
/// An NFT was minted.
NftMint,
/// An NFT was transferred.
NftTransferTo,
/// An NFT listing was canceled.
NftCancelListing,
/// End of a batch NFT listing.
NftEndBatchListing,
/// AVT growth was lifted.
AvtGrowthLifted,
/// A claim for lower AVT was executed.
AvtLowerClaimed,
/// A lift operation to the prediction market.
LiftedToPredictionMarket,
/// Secondary event emitted by the ERC-20 token contract.
Erc20DirectTransfer,
}

impl ValidEvents {
Expand Down Expand Up @@ -123,6 +148,10 @@ impl ValidEvents {
// hex string of Keccak-256 for LogLowerClaimed(uint32)
ValidEvents::AvtLowerClaimed =>
H256(hex!("9853e4c075911a10a89a0f7a46bac6f8a246c4e9152480d16d86aa6a2391a4f1")),

// hex string of Keccak-256 for Transfer(address,address,uint256)
ValidEvents::Erc20DirectTransfer =>
H256(hex!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")),
}
}

Expand All @@ -145,6 +174,8 @@ impl ValidEvents {
return Some(ValidEvents::AvtLowerClaimed)
} else if signature == &ValidEvents::LiftedToPredictionMarket.signature() {
return Some(ValidEvents::LiftedToPredictionMarket)
} else if signature == &ValidEvents::Erc20DirectTransfer.signature() {
return Some(ValidEvents::Erc20DirectTransfer)
} else {
return None
}
Expand All @@ -159,6 +190,17 @@ impl ValidEvents {
_ => false,
}
}

pub fn values() -> Vec<ValidEvents> {
ValidEvents::iter().collect()
}

pub fn is_primary(&self) -> bool {
match *self {
ValidEvents::Erc20DirectTransfer => false,
_ => true,
}
}
}

#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, Eq, TypeInfo, MaxEncodedLen)]
Expand Down Expand Up @@ -308,67 +350,66 @@ impl LiftedData {
}
}

// T1 Event definition:
// event LogLiftedToPredictionMarket(address indexed tokenContract, bytes32 indexed t2PubKey,
// uint256 amount);
#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, Eq, TypeInfo, MaxEncodedLen)]
pub struct LiftedToPredictionMarketData {
pub token_contract: H160,
pub receiver_address: H256,
pub amount: u128,
}

impl LiftedToPredictionMarketData {
const TOPIC_CURRENCY_CONTRACT: usize = 1;
const TOPIC_INDEX_T2_ADDRESS: usize = 2;

pub fn is_valid(&self) -> bool {
return !self.token_contract.is_zero() && !self.receiver_address.is_zero()
}
impl LiftedData {
const TOPIC_T1_FROM_ADDRESS: usize = 1;
const TOPIC_BRIDGE_CONTRACT: usize = 2;

pub fn parse_bytes(data: Option<Vec<u8>>, topics: Vec<Vec<u8>>) -> Result<Self, Error> {
pub fn from_erc_20_contract_transfer_bytes(
data: Option<Vec<u8>>,
topics: Vec<Vec<u8>>,
) -> Result<Self, Error> {
// Structure of input bytes:
// data --> amount (32 bytes) (big endian)
// all topics are 32 bytes long
// topics[0] --> event signature (can be ignored)
// topics[1] --> currency contract address (first 12 bytes are 0 and should be ignored)
// topics[2] --> receiver t2 public key (32 bytes)
// topics[1] --> ethereum sender address
// topics[2] --> ethereum receiver address (bridge contract)

if data.is_none() {
return Err(Error::LiftedToPredictionMarketEventMissingData)
return Err(Error::LiftedEventMissingData)
}
let data = data.expect("Already checked for errors");

if data.len() != WORD_LENGTH {
return Err(Error::LiftedToPredictionMarketEventBadDataLength)
return Err(Error::LiftedEventBadDataLength)
}

if topics.len() != 3 {
return Err(Error::LiftedToPredictionMarketEventWrongTopicCount)
return Err(Error::LiftedEventWrongTopicCount)
}

if topics[Self::TOPIC_CURRENCY_CONTRACT].len() != WORD_LENGTH ||
topics[Self::TOPIC_INDEX_T2_ADDRESS].len() != WORD_LENGTH
if topics[Self::TOPIC_BRIDGE_CONTRACT].len() != WORD_LENGTH ||
topics[Self::TOPIC_T1_FROM_ADDRESS].len() != WORD_LENGTH
{
return Err(Error::LiftedToPredictionMarketEventBadTopicLength)
return Err(Error::LiftedEventBadTopicLength)
}

let token_contract = H160::from_slice(
&topics[Self::TOPIC_CURRENCY_CONTRACT][DISCARDED_ZERO_BYTES..WORD_LENGTH],
let bridge_contract = H160::from_slice(
&topics[Self::TOPIC_BRIDGE_CONTRACT][DISCARDED_ZERO_BYTES..WORD_LENGTH],
);

let receiver_address = H256::from_slice(&topics[Self::TOPIC_INDEX_T2_ADDRESS]);
let sender_address = H160::from_slice(
&topics[Self::TOPIC_T1_FROM_ADDRESS][DISCARDED_ZERO_BYTES..WORD_LENGTH],
);

if data[0..HALF_WORD_LENGTH].iter().any(|byte| byte > &0) {
return Err(Error::LiftedToPredictionMarketEventDataOverflow)
return Err(Error::LiftedEventDataOverflow)
}

let amount = u128::from_be_bytes(
data[HALF_WORD_LENGTH..WORD_LENGTH]
.try_into()
.expect("Slice is the correct size"),
);
return Ok(LiftedToPredictionMarketData { token_contract, receiver_address, amount })
return Ok(LiftedData {
token_contract: H160::zero(),
sender_address,
// This must be overwritten after the lift reach consensus with the account configured
// on the pallet
receiver_address: bridge_contract.into(),
amount,
nonce: U256::zero(),
})
}
}

Expand Down Expand Up @@ -698,13 +739,15 @@ pub enum EventData {
LogNftEndBatchListing(NftEndBatchListingData),
LogAvtGrowthLifted(AvtGrowthLiftedData),
LogLowerClaimed(AvtLowerClaimedData),
LogLiftedToPredictionMarket(LiftedToPredictionMarketData),
LogLiftedToPredictionMarket(LiftedData),
LogErc20Transfer(LiftedData),
}

impl EventData {
#[allow(unreachable_patterns)]
pub fn is_valid(&self) -> bool {
return match self {
// LogLowerClaimed missing. TODO add and remove unreachable patterns.
EventData::LogAddedValidator(d) => d.is_valid(),
EventData::LogLifted(d) => d.is_valid(),
EventData::LogNftMinted(d) => d.is_valid(),
Expand All @@ -713,6 +756,7 @@ impl EventData {
EventData::LogNftEndBatchListing(d) => d.is_valid(),
EventData::LogAvtGrowthLifted(d) => d.is_valid(),
EventData::LogLiftedToPredictionMarket(d) => d.is_valid(),
EventData::LogErc20Transfer(d) => d.is_valid(),
EventData::EmptyEvent => true,
_ => false,
}
Expand Down
2 changes: 1 addition & 1 deletion primitives/avn-common/src/tests/test_event_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ fn test_prediction_market_lifted_avt_parse_bytes_good_case() {

let data = Some(get_lifted_avt_data());
let topics = get_lifted_avt_topics();
let result = LiftedToPredictionMarketData::parse_bytes(data, topics);
let result = LiftedData::parse_bytes(data, topics);

assert!(result.is_ok());
let result = result.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion runtime/avn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ use sp_std::collections::btree_set::BTreeSet;

pub struct EthBridgeAvnRuntimeEventsFilter;
impl EthereumEventsFilterTrait for EthBridgeAvnRuntimeEventsFilter {
fn get_filter() -> EthBridgeEventsFilter {
fn get() -> EthBridgeEventsFilter {
let allowed_events: BTreeSet<ValidEvents> =
vec![ValidEvents::AvtLowerClaimed, ValidEvents::Lifted].into_iter().collect();

Expand Down
2 changes: 1 addition & 1 deletion runtime/test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ where

pub struct EthBridgeTestRuntimeEventsFilter;
impl EthereumEventsFilterTrait for EthBridgeTestRuntimeEventsFilter {
fn get_filter() -> EthBridgeEventsFilter {
fn get() -> EthBridgeEventsFilter {
let allowed_events: BTreeSet<ValidEvents> = vec![
ValidEvents::AddedValidator,
ValidEvents::Lifted,
Expand Down

0 comments on commit 5fca822

Please sign in to comment.