-
Notifications
You must be signed in to change notification settings - Fork 11.2k
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
Rosetta: Handle single GasCoin transfers #20592
base: main
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
3 Skipped Deployments
|
crates/sui-rosetta/src/operations.rs
Outdated
for command in commands { | ||
match command { | ||
SuiCommand::TransferObjects(objs, _) => { | ||
if objs.len() == 1 { |
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.
Could we instead have this feature handle well if any of the transferred objects is SuiArgument::GasCoin
, instead of only when the only object transferred is a gas-coin?
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.
This ofc would need changes below as well. What I am thinking of is checking whether a Gas transfer exists. If it does and it can be matched with balance-changes between gas-coin-owner and recipient, then we replace the two with the appropriate PaySui
and correct Gas
operations.
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.
Handles if any of the transferred objects is GasCoin
crates/sui-rosetta/src/operations.rs
Outdated
Some(amount) => amount.value, | ||
None => 0, | ||
}; | ||
if account.address == sender { |
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.
As @patrickkuo pointed out, we shouldn't use sender but previous gas-coin owner.
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.
Done
if account.address == sender { | ||
// sender's balance needs to be adjusted for gas | ||
amount -= gas_used; | ||
} else { |
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.
We should not edit every balance-change. Only the gas-coin sender and the recipient ones.
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.
And we should also make sure that these addition and subtraction each happen only once.
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.
Done
crates/sui-rosetta/src/operations.rs
Outdated
let mut is_single_gascoin_transfer = false; | ||
match tx { | ||
SuiTransactionBlockKind::ProgrammableTransaction(pt) => { | ||
let SuiProgrammableTransactionBlock { | ||
inputs: _, | ||
commands, | ||
} = &pt; | ||
for command in commands { | ||
match command { | ||
SuiCommand::TransferObjects(objs, _) => { | ||
if objs.len() == 1 { | ||
match objs[0] { | ||
SuiArgument::GasCoin => { | ||
is_single_gascoin_transfer = true; | ||
} | ||
_ => {} | ||
} | ||
} | ||
} | ||
_ => {} | ||
} | ||
} | ||
} | ||
_ => {} | ||
} |
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.
lets extract this to a function to make it more readable.
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've extracted it to a function.
crates/sui-rosetta/src/operations.rs
Outdated
inputs: _, | ||
commands, | ||
} = &pt; | ||
for command in commands { |
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.
In case we find a command with gas-coin we can stop iterating.
Maybe something like commands.into_iter().find(|c| { ... })
would be more suitable.
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.
Done
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.
Argument::GasCoin
can be have any index in the transfer-args input. We need to catch these cases as well.- We need to make sure that we do not double-count the gas used in case more than one
SuiPay
orSuiBalanceChange
refer to the gas sender-recipient pair.
crates/sui-rosetta/src/operations.rs
Outdated
.into_iter() | ||
.find(|command| match command { | ||
SuiCommand::TransferObjects(objs, _) => { | ||
objs.len() > 0 && objs[0] == SuiArgument::GasCoin |
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.
This would fail if the GasCoin
is not the first object transferred.
Something like objs.find(|&obj| obj == SuiArgument::GasCoin).is_some()
objs.into_iter().any(|&obj| obj == SuiArgument::GasCoin)
would succeed in case any of the objects transferred is the GasCoin
.
I guess as we should identify such transfers, we can also rename the function to is_gascoin_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.
Added a search for any place for GasCoin and renamed the function to is_gascoin_transfer
// who paid for the txn (this is the format Rosetta wants to process) | ||
operations.push(Operation::gas(prev_gas_owner, gas_used)) | ||
} | ||
OperationType::SuiBalanceChange => { |
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.
What happens if multiple balance changes exist there for sender or recipient? I think this block might double-count gas.
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.
balance_changes are derived from effects
field and even though there were multiple transfers for sender/recipient, there is one entry per address so I think there is no risk of double-count here.
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.
Could there be the case though, that the sender also receives an amount equal to the gas-coin transferred, so that there is no balance-change for them? I know that this is far-fetched, but every SUI tx passes through this parsing. In that case this operation mapping would fail, correct? Lastly, the PaySui
operations need to have their amounts match. See also above comment about explicit handling versus mapping.
Edit: Also even if the amount is not equal, it would introduce a discrepancy between the PaySui
for receiver and the PaySui
for sender operations.
Example ptb:
- SUI is unlocked from somewhere (eg. a Liquidity Pool),
- merged with Gas-coin
- gas is sent to the recipient.
tx.data.transaction().clone(), | ||
tx.data.gas_data().owner, | ||
gas_owner, | ||
gas_used, | ||
); | ||
|
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.
Every transaction that happens on-chain will pass from here. We need to be extra careful that we do not mess up the balance-changes. Does it make sense to add a sanity check that operations balance changes match the tx balance changes after editing the operations with gas-coins transfer?
@patrickkuo , @nonvis wdyt?
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 don't think a sanity check is required. First, the balance-changes
is edited only when GasCoin is referenced for transferObjects, which occurs infrequently. And if they do occur, only two types of operations are used: BalanceChange
and Gas
. process_gascoin_transfer
function processes these two types of operations.
So in terms of the number of operations stored before/after process_gascoin_transfer
, they would be the same.
} | ||
}); | ||
assert_eq!(balance, 0); | ||
} |
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.
Optimally, a more complex transaction involving gas-coin transfer should be tested too. Something involving a balance-change from eg. a DEX tx along with a gas-coin transfer. I know this is an extreme edge-case, but if not every, almost every tx on-chain will actually be passed through this endpoint.
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 we can copy an existing DEX tx from chain and try manually adding a gas-coin-transfer to it?
…d in to objectTransfers
…perations are processed
crates/sui-rosetta/src/operations.rs
Outdated
} | ||
}); | ||
} | ||
_ => {} |
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.
Theoretically this should be unreachable, correct? If so, can we return an Error here?
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.
Done. Returns an Error
crates/sui-rosetta/src/operations.rs
Outdated
operation.account.map(|account| { | ||
let mut amount = match operation.amount { | ||
Some(amount) => amount, | ||
None => return, |
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.
Here we ignore an Operation. The operation will be removed from the operations list. Is this case unreachable? If so, can we return an error with the tx-digest?
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.
Done. I made it return an Error so that it won't be secretly removed from the operations list.
crates/sui-rosetta/src/operations.rs
Outdated
_ => {} | ||
} | ||
}); | ||
// sanity check to make sure all the operations have been processed |
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 don't see any way that this iterator is not consumed. By sanity check I meant checking that tx-balance-changes much the ones the operations refer to.
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.
Removed the sanity check
crates/sui-rosetta/src/operations.rs
Outdated
) -> Result<Vec<Operation>, anyhow::Error> { | ||
let mut gascoin_transfer_operations = vec![]; | ||
if Self::is_gascoin_transfer(tx) { | ||
coin_change_operations.into_iter().for_each(|operation| { |
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 am wondering whether it would be cleaner, to replace this mapping of balance-changes to a more explicit approach:
- Check whether
is_gascoin_transfer
. - Check whether SUI balance-changes (
SuiBalanceChange
with SUI) exist between sender and recipient of gas-object, with a matching amount bigger than gas-used. - If both of the above are true, edit these 3 operations:
- recipient Gas
- sender balance-change of SUI -> PaySui
- recipient balance-change of SUI -> PaySui,
otherwise if 1 is true but 2 is not*,create the missing PaySui operation. Maybe create two new SuiBalanceChange operations (other balance-changes might exist for sender/recipient, for different reasons).
*A little far-fetched, but the sender could have received the exact amount of the gas-coin (no-balance-change), or the sender could have received a larger balance than the gas-coin, making it a PaySui which increases the sender's balance. This shouldn't happen.
Also due to the nature of ptbs, there might be the case that even the recipient could receive more balance than the one which was transferred by the gas-coin, for other reasons.
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.
@patrickkuo , @nonvis wdyt? I believe explicitly editing the operations in question, instead of mapping all the operations is a better approach.
// who paid for the txn (this is the format Rosetta wants to process) | ||
operations.push(Operation::gas(prev_gas_owner, gas_used)) | ||
} | ||
OperationType::SuiBalanceChange => { |
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.
Could there be the case though, that the sender also receives an amount equal to the gas-coin transferred, so that there is no balance-change for them? I know that this is far-fetched, but every SUI tx passes through this parsing. In that case this operation mapping would fail, correct? Lastly, the PaySui
operations need to have their amounts match. See also above comment about explicit handling versus mapping.
Edit: Also even if the amount is not equal, it would introduce a discrepancy between the PaySui
for receiver and the PaySui
for sender operations.
Example ptb:
- SUI is unlocked from somewhere (eg. a Liquidity Pool),
- merged with Gas-coin
- gas is sent to the recipient.
…uietly discarding operations
Description
Describe the changes or additions included in this PR.
Test plan
How did you test the new or updated feature?
Release notes
Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required.
For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates.