-
Notifications
You must be signed in to change notification settings - Fork 242
Add support for mint, burn, and transfer #215
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
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #215 +/- ##
===========================================
+ Coverage 99.89% 100.00% +0.10%
===========================================
Files 207 214 +7
Lines 11335 11755 +420
===========================================
+ Hits 11323 11755 +432
+ Misses 11 0 -11
+ Partials 1 0 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
|
| if err := am.database.UpsertTokenTransfer(ctx, transfer); err != nil { | ||
| log.L(ctx).Errorf("Failed to record token transfer '%s': %s", transfer.ProtocolID, err) | ||
| return err | ||
| } |
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.
The logic below is relying strongly on the transactional nature of the database in order to end up with the correct account balances even in the face of potential retries. Just want to confirm that's OK.
Side note: The ERC1155 plugin actually allows balance querying, so we could include the ending balance with each transfer event instead of just the delta... but that seems more complex on the plugin side and probably won't be possible with other plugins, e.g. a UTXO model.
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.
Well, I started typing: "Given this would need to be implemented differently for each database type, I wonder if it's worth pushing down to the database layer." ... then realized below you've done that already 👍
For NoSQL, for example you'd likely put the increment operation into an atomic instruction in the DB, rather than using a multi-query transction semantic (as we're doing with SQL, under the RunAsGroup).
peterbroadhurst
left a comment
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.
... see notes about identity mapping. Want to make sure we've got our ducks in a row on that item, before going any further with the review.
db/migrations/postgres/000030_create_tokentransfer_table.up.sql
Outdated
Show resolved
Hide resolved
|
@peterbroadhurst thanks for the initial look. I was already certain that my handling of identities was wrong, so the comments are helpful to steer my thinking a bit. I'll come up to speed on where #192 is at - agree we probably want to get that shaped up for merge before this propagates the existing problems further. |
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
Also enable sync-async calls for token transfers (and honor the "confirm" query parameter). Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
|
This is now fully reworked on top of the identity manager changes. |
a58d206 to
48fa0c0
Compare
peterbroadhurst
left a comment
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.
... intermediate comment while I continue through the code here
| if err := am.database.UpsertTokenTransfer(ctx, transfer); err != nil { | ||
| log.L(ctx).Errorf("Failed to record token transfer '%s': %s", transfer.ProtocolID, err) | ||
| return err | ||
| } |
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.
Well, I started typing: "Given this would need to be implemented differently for each database type, I wonder if it's worth pushing down to the database layer." ... then realized below you've done that already 👍
For NoSQL, for example you'd likely put the increment operation into an atomic instruction in the DB, rather than using a multi-query transction semantic (as we're doing with SQL, under the RunAsGroup).
| } | ||
| if transfer.Type != fftypes.TokenTransferTypeMint { | ||
| balance.Identity = transfer.From | ||
| balance.Amount = -transfer.Amount |
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.
Have you considered the datatype for Amount?
e.g. is a 64bit int considered large enough, when decimals are taken into account. Or do we need a big.Int represented as a String on the JSON (and appropriate backend DB storage).
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.
Yea, the same thing has been nagging at me. I suspect we may want Big Int. Perhaps worth a consult from @jimthematrix as well?
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.
from the ethereum side, amount values are represented in uint256, with web3 golang SDKs using a binding with big.Int. that should be sufficient to inform our decision here.
in addition, in support of conventions in that community, we should consider supporting both decimal and hexadecimal (required to have 0x prefixes) strings for inputs.
Will allow for tying transfers to messages in a future commit. Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
peterbroadhurst
left a comment
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.
Ok - worked through all the changes... and found myself raising a spelling one for you to ponder. But it's not necessary to resolve in this PR, as the Tokens work is still overall in a pre-release flux state.
| "tokenindex": &StringField{}, | ||
| "identity": &StringField{}, | ||
| "balance": &Int64Field{}, | ||
| "poolprotocolid": &StringField{}, |
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.
How will local_id in the database interact with the query filters?
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 commit for this
pkg/fftypes/operation.go
Outdated
| OpTypeTokensTransfer OpType = ffEnum("optype", "tokens_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.
I note a difference in the OpType and EventType semantics of:
- Events
token_ - Ops:
tokens_
Wonder if that's intentional and makes sense to be different, or if we should pick one of the two 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.
You're right, should probably make this consistent.
| TokenIndex string `json:"tokenIndex,omitempty"` | ||
| Identity string `json:"identity,omitempty"` | ||
| Balance int64 `json:"balance"` | ||
| PoolProtocolID string `json:"poolProtocolId,omitempty"` |
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.
Having I'm sure been part of suggesting it, I'm finding protocolId (particularly when uplifted to poolProtocolId) a bit weird in this context.
I wonder if in Tokens there's something simpler. Whether just identifier (/poolIdentifier) would make sense (as it's the thing you should use in all cases), and then maybe ffid for the local_id field (which we've discussed isn't really local in all cases).
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 Identifier would be bad since pools already have an ID field (and we need to specifically differentiate the ID assigned by FireFly from the one assigned by the connector). We do use BackendID in some cases (I think just on Operations) - not sure if that seems any better/worse that ProtocolID in this instance?
And as it's evolving, I think local_id is likely to be local-only (ie ultimately different on all nodes), so I think leaving that for the time being is correct.
internal/tokens/fftokens/fftokens.go
Outdated
| type mintTokens struct { | ||
| PoolID string `json:"poolId"` | ||
| To string `json:"to"` | ||
| Amount int64 `json:"amount"` |
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 discussed above, should use big.Int here to accommodate possible range with Ethereum uint256
| eventName := "Transfer" | ||
| if t == fftypes.TokenTransferTypeMint { | ||
| eventName = "Mint" | ||
| } |
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.
missing the branch for when the transfer type is Burn?
| if tokenIndex == "" || | ||
| poolProtocolID == "" || | ||
| operatorAddress == "" || | ||
| value == "" || |
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 must be confused here, I thought tokenIndex is only for non-fungibles, and value (for amount) is only for fungibles, so one of them is always going to be empty?
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.
The API agreement with the connector states that tokenIndex will always be included in the websocket events sent back to FireFly. My particular ERC1155 connector will always send tokenIndex=0 for operations on fungible pools.
Would not require a change here, but it occurs to me that I could change the input of the ERC1155 connector to work without specifying tokenIndex for transfers in fungible pools. That would remove the need for the user to specify tokenIndex in the REST call (currently the call will fail at the connector level if tokenIndex is missing).
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.
Actually I guess I could make tokenIndex optional throughout this code as well, if it was optional to the connector.
internal/tokens/fftokens/fftokens.go
Outdated
| SetBody(&mintTokens{ | ||
| PoolID: mint.PoolProtocolID, | ||
| To: mint.To, | ||
| Amount: mint.Amount, |
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 is not obvious for non-fungible tokens, if there's only one integer value, is that treated as the token index, or the count of tokens to be minted (with the indexes to be assigned by the contract)?
we should allow the input to specify the desired index to mint for an NFT. but not sure if that's possible with the current 1155 contract implementation
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 is not possible with the current Solidity contract. It always mints the next available index in the pool.
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
This is not relevant for fungible pools, so should be optional at the discretion of the token connector. Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
|
Raised https://github.com/hyperledger/firefly-cli/issues/109 to track E2E failure |
|
E2E tests can be retriggered after hyperledger/firefly-ethconnect#160 is integrated. |
peterbroadhurst
left a comment
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.
Willing to be told I'm wrong here, but a 1024 character VARCHAR field feels wrong to me for an integer column. I understand big.Int is arbitrary precision and as such a base 10 string representation could get very long.
For me it feels like we should define a fftypes.BigInt type, and have on it Scan and Value functions that are safe in terms of the bounds that we set (like we do for UUID in https://github.com/hyperledger/firefly/blob/main/pkg/fftypes/uuid.go)
Then it also should have a deliberate choice on the JSON side (marshal/unmarshal) as to how we represent it on the JSON on output, and what formats we accept input from.
My gut says we should:
- Cap it at 32byte values
- DB serialize it as a Hex string (so it's fixed up to 64 bytes)
- JSON serialize it as base-10 string by default
- Write the code so if we wanted to provide a different option for Hex/Number return we could do that via FireFly config
- Support deserializing it from JSON per the rules here:
I'd be happy to help with this, if you agree @awrichar ?
|
@peterbroadhurst yep, I'm not sure where the max decimal representation for uint256 comes out, but I think it's somewhere in the 70-80 digit range. 1024 is excessive but seemed to be what we've used in many spots. Happy to have some helpers for marshaling and unmarshaling big int though if you want to take that on! |
|
Thanks @awrichar I'll add a commit as such to this branch, and ask @jimthematrix for a review before merging. |
Signed-off-by: Peter Broadhurst <peter.broadhurst@kaleido.io>
|
I've added in an Not wired up to the other types yet. |
Signed-off-by: Peter Broadhurst <peter.broadhurst@kaleido.io>
Signed-off-by: Peter Broadhurst <peter.broadhurst@kaleido.io>
|
I've made the changes here discussed on BigInt consistency. |
|
Latest error from tokens connector after hyperledger/firefly-tokens-erc1155#32: |
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.
Looks good besides the failing E2E. Hopefully that's not too hard to track down and fix.
|
BigInt changes look great @peterbroadhurst; thanks for handling this and the piece in the tokens connector. |
No description provided.