-
Notifications
You must be signed in to change notification settings - Fork 94
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
feat(nft-swap): nft swap protocol v2 POC #2084
Conversation
… and test nft abi. Make NFT type uppercase in CoinProtocol
…5_TEST_TOKEN_BYTES from formatted code
…data for Erc1155 transfer
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.
Great start! We need to start paying more attention to the design choices early on before we commit to a suboptimal one.
mm2src/coins/eth.rs
Outdated
self.rlp_append(&mut stream); | ||
// there is minimal but risk that stream.out() may panic | ||
debug_assert!(stream.is_finished(), "RlpStream must be finished before calling out"); | ||
Vec::from(stream.out()) |
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.
Maybe use an empty vec if stream is not finished, this way we will avoid panicking and the preimage will be validated anyway and swap will fail if there is any problems in serialization.
P.S. Panicking here can be an attack vector if we are not careful since the preimage is received from the other side through a p2p message and can be made to crash the other side's node, right now it's won't be a problem probably since we use parse_preimage
first but it can be a problem if we are not careful with code changes.
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.
Maybe use an empty vec if stream is not finished
Oh, exactly! yep we can do if stream.is_finished()
check
right now it's won't be a problem probably since we use parse_preimage first but it can be a problem if we are not careful with code changes.
I see, we can refactor trait ToBytes impl and return Result<Vec, Error>
. Well, it will trigger other code parts, so in some other traits we also have to do the refactoring and return Result type. For now I suggest to leave stream.is_finished()
check,
impl ToBytes for SignedEthTx {
fn to_bytes(&self) -> Vec<u8> {
let mut stream = RlpStream::new();
self.rlp_append(&mut stream);
// Handle potential panicking.
if stream.is_finished() {
Vec::from(stream.out())
} else {
// TODO: Consider returning Result<Vec<u8>, Error> in future refactoring for better error handling.
warn!("RlpStream was not finished; returning an empty Vec as a fail-safe.");
vec![]
}
}
}
and in next pr(s) do the refactor and return Result where we will have to. Or at least try to refactor and see, do we actually have problems with it or not. I think its better to propagate errors instead of being careful (I mean we all are humans and there is a risk of miss something).
UPD: added stream.is_finished()
check for now
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.
I think it's better to propagate errors instead of being careful (I mean we all are humans and there is a risk of miss something).
I agree, I have the same problem with my_addr
function in EVM HD wallet PR
komodo-defi-framework/mm2src/coins/utxo.rs
Lines 1056 to 1066 in d085af1
async fn my_addr(&self) -> Self::Address { | |
match &self.as_ref().derivation_method { | |
DerivationMethod::SingleAddress(addr) => addr.clone(), | |
// Todo: Expect should not fail but we need to handle it properly | |
DerivationMethod::HDWallet(hd_wallet) => hd_wallet | |
.get_enabled_address() | |
.await | |
.expect("Getting enabled address should not fail!") | |
.address(), | |
} | |
} |
To modify it I need to follow the same design choices @artemii235 did in the state machine swaps implementation, he would rather save a value to be retrieved later instead of calling the same function in multiple places and doing error handling in multiple places for the same function which makes the code harder to understand, and for such critical parts we need the code to be as clear as possible.
mm2src/coins/lp_coins.rs
Outdated
/// Defines associated types specific to each coin (Pubkey, Address, etc.) | ||
pub trait NftAssocTypes { | ||
type TokenContractAddr: Send + Sync + fmt::Display; | ||
type TokenContractAddrParseError: fmt::Debug + Send + fmt::Display; | ||
type TokenId: ToBytes + Send + Sync; | ||
type TokenIdParseError: fmt::Debug + Send + fmt::Display; | ||
type Chain: ToBytes + Send + Sync; | ||
type ChainParseError: fmt::Debug + Send + fmt::Display; | ||
type ContractType: ToBytes + Send + Sync; | ||
type ContractTypeParseError: fmt::Debug + Send + fmt::Display; | ||
|
||
fn parse_token_contract_address( | ||
&self, | ||
token_contract_addr: &[u8], | ||
) -> Result<Self::TokenContractAddr, Self::TokenContractAddrParseError>; | ||
|
||
fn parse_token_id(&self, token_id: &[u8]) -> Result<Self::TokenId, Self::TokenIdParseError>; | ||
|
||
fn parse_chain(&self, chain: &[u8]) -> Result<Self::Chain, Self::ChainParseError>; | ||
|
||
fn parse_contract_type(&self, contract_type: &[u8]) -> Result<Self::ContractType, Self::ContractTypeParseError>; | ||
} |
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.
Will we actually need those in swaps code? I am not sure yet, parsing data is used for p2p data and from storage data so maybe we will need for the from storage data but for p2p data I think the TokenContractAddr/TokenId/Chain/ContractType will be known for both sides before starting the swap. We can leave them for now but we have to be careful with adding coin specific logic inside swap state machine logic which we did in legacy swap and want to avoid in v2 code to make it cleaner.
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.
I see two options of nft swap implementation:
-
Have optional nft fields in current args structures, for example in
SendMakerPaymentArgs
. So in maker state changes we will need to check do we have Some in nft related fields or None. So in each maker state we will have to handle two inner implementations: for coin and for NFT. We may have the same problem as we had during trying to integrate nft activation into existing coin related code, which will lead to decision of logic separation. -
Create separate NFT related structures for args (eg
SendNftMakerPaymentArgs
) and NFT Maker states forMakerSwapStateMachine
. This way we wont need to check optional nft fields in args as all of them will be required. Also we wont need to handle the coin and nft behaviors in currenton_changed
functions for coin maker states, as we will need to implementNFT related states
. I believe we will still have samestruct MakerSwapStateMachine
.
I think the TokenContractAddr/TokenId/Chain/ContractType will be known for both sides before starting the swap
I wasnt sure about it so just decided to prepare the functionality. As we already use parse_...
functions in maker_swap_v2.rs
and taker_swap_v2.rs
I decided that we may have nft related fields in args
structures or negotiation
data, so we need this.
The another big approach of nft implementation is providing in args structure
(doesnt matter for now in NFT args or in optional nft field in current ags
structures, its about having or not having NftAssocTypes
) the string of concatenated "token_address,token_id".
This way Maker coin should be NFT global "coin" from the beginning I think, from its "nfts_infos"
field we can get NftInfo
value using "token_address,token_id" key. You know, this approach sounds not bad. This way we wont need NftAssocTypes
. Need to refactor NFT activation and enable global NFT using nft json from coin config file.
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.
Create separate NFT related structures for args (eg SendNftMakerPaymentArgs) and NFT Maker states for MakerSwapStateMachine
NFT states for swap state machines is a nice idea if it can be done cleanly :)
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.
I wasn't sure about it so just decided to prepare the functionality.
We can leave it in this PR and re-assess when implementing NFT functionalities in the swaps state machine.
mm2src/coins/lp_coins.rs
Outdated
@@ -1502,6 +1544,31 @@ pub struct SendMakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { | |||
pub swap_unique_data: &'a [u8], | |||
} | |||
|
|||
pub struct SendNftMakerPaymentArgs<'a, Coin: CoinAssocTypes + NftAssocTypes + ?Sized> { |
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.
Again, we shouldn't have maker payment args specific for NFTs. It will create a lot of conditional code in swaps code, you can leave it for now for the P.O.C. purpose but we need to have a plan on how we will integrate NFT swaps logic in current swap state machine.
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.
Yep, its just one of two ways of two nft implementations from this #2084 (comment) description
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.
but we need to have a plan on how we will integrate NFT swaps logic in current swap state machine.
Oke, then we can think about approach with concatenated string of "token_address,token_id"
parameter in Maker Payment args. If we want to reutulize the existing code, then we can add is_nft:bool
param and optional nft_key:String
.
If is_nft:true
, nft_key
must be Some, and we try to get NftInfo
value from nfts_infos
in global NFT. Well this means we will have to handle nft behavior and coin in same States
.
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.
If is_nft:true, nft_key must be Some, and we try to get NftInfo value from nfts_infos in global NFT. Well this means we will have to handle nft behavior and coin in same States.
Answered here #2084 (comment) , we will re-assess in next PRs I guess.
mm2src/coins/lp_coins.rs
Outdated
@@ -1575,6 +1642,38 @@ pub trait MakerCoinSwapOpsV2: CoinAssocTypes + Send + Sync + 'static { | |||
async fn spend_maker_payment_v2(&self, args: SpendMakerPaymentArgs<'_, Self>) -> Result<Self::Tx, TransactionErr>; | |||
} | |||
|
|||
#[async_trait] | |||
pub trait MakerNftSwapOpsV2: CoinAssocTypes + NftAssocTypes + Send + Sync + 'static { |
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.
Same comment, are you planning to make a different maker_swap_v2.rs
and taker_swap_v2.rs
files for NFTs?
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.
No, I wanted only to have NFT related states in maker_swap_v2.rs
and taker_swap_v2.rs
for MakerSwapStateMachine
and TakerSwapStateMachine
.
Well yeah for nft states we will need to require NFT related trait impl here impl<MakerCoin: MmCoin + MakerCoinSwapOpsV2, TakerCoin: MmCoin + TakerCoinSwapOpsV2> State
But we will discuss the nft swap approach of course.
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.
Next review iteration! Probably the last one :)
…ContractValidation in validate_payment_args
…index_bytes, erc721_transfer_with_data standalone non public
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.
🔥
@borngraced if you don't have anymore comments and all your review notes are resolved please approve this PR. |
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.
Great work!
here's my last review iteration
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.
one more note please :)
mm2src/coins/lp_coins.rs
Outdated
pub trait NftAssocTypes { | ||
type ContractAddress: Send + Sync + fmt::Display; | ||
type ContractAddrParseError: fmt::Debug + Send + fmt::Display; | ||
type TokenId: ToBytes + Send + Sync; | ||
type TokenIdParseError: fmt::Debug + Send + fmt::Display; | ||
type ContractType: ToBytes + Send + Sync; | ||
type ContractTypeParseError: fmt::Debug + Send + fmt::Display; | ||
|
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.
why can't we have just one error type that covers all error types? I mean a single, more informative error enum that encapsulates these different kinds of errors
e.g
type Error: fmt::Debug + Send + fmt::Display;
Also, this trait name is better as NftParser
or something
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.
why can't we have just one error type that covers all error types?
I followed the approach from CoinAssocTypes
trait
komodo-defi-framework/mm2src/coins/utxo.rs
Lines 1051 to 1061 in 76af668
impl<T: UtxoCommonOps> CoinAssocTypes for T { | |
type Address = Address; | |
type AddressParseError = MmError<AddrFromStrError>; | |
type Pubkey = Public; | |
type PubkeyParseError = MmError<keys::Error>; | |
type Tx = UtxoTx; | |
type TxParseError = MmError<serialization::Error>; | |
type Preimage = UtxoTx; | |
type PreimageParseError = MmError<serialization::Error>; | |
type Sig = Signature; | |
type SigParseError = MmError<secp256k1::Error>; |
I suppose Artem decided to keep error types separately, as for UTXO he had errors from crates and usually our own enum errors already implement From for such types.
However, I agree that we can leave one error type for nft assoc trait, because usually we have to create our own structures/enum for this feature, so we cant avoid From trait implementation.
added one err here 95411dc also renamed eth error as its related to eth coin.
Also, this trait name is better as NftParser or something
I would like to disagree, CoinAssocTypes
and NftAssocTypes
traits are created to parse specific types associated with Coin or with NFT instances. we dont get the whole Coin or Nft.
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.
I would like to disagree, CoinAssocTypes and NftAssocTypes traits are created to parse specific types associated with Coin or with NFT instances. we dont get the whole Coin or Nft.
doesn't mean it's a good name imo.. they can be rename to ParseCoinAssocTypes
, ParseNftAssocTypes
. currently, no one will ever guess what the trait is suppose to do just by looking at the name..I mean it doesn't tell any meaning
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.
LGTM 🚀 Thank you very much for the great work!
* dev: feat(nft-swap): nft swap protocol v2 POC (KomodoPlatform#2084) fix(zcoin): syncing and activation improvements (KomodoPlatform#2089) feat(crypto): mnemonic generation/encryption/decryption/storage (KomodoPlatform#2014) fix(eth): error handling in RPCs (KomodoPlatform#2090)
Related to #900
To deploy smart contract you need to have it's abi json and bytes. They can be compiled from smart contract solidity code.
nft_swap_contract_abi.json
andNFT_SWAP_CONTRACT_BYTES
are compiled fromEtomicSwapNft.sol
contract from this PRSolidity code of test ERC721 and ERC1155 tokens from which
abi jsons
andbytes
were compiled:You can get
abi json & bytes
easily using https://remix.ethereum.org/ web IDE (PS dont forget to format code withshift+alt+F
pls)Some small note:
There was a moment that I wanted to validate event and topics of
onERC1155Received
andonERC721Received
functions. But if payment was confirmed and its state isMakerPaymentSent
in validation function, there is no need to checkon...Received
events.Still would like to share a nice article about logs, topics and data objects in ETH receipt.
https://medium.com/mycrypto/understanding-event-logs-on-the-ethereum-blockchain-f4ae7ba50378