Transaction

42CCEA220C47C1…2C810B8B60DE

Block 747,729 · index 0 · indexed

Summary

Hash
42CCEA220C47C158FE78338C648EB64C010F33FF162F4D4314762C810B8B60DE
Block
747,729
Size
13165 bytes
Gas used
22,288,331 / 44,576,602
Fee
44577 ugnot
Status
success

Messages

#1AddPackagegno.land/p/akkadia/beta/grc115515 arguments

Arguments · 15

  1. #1grc1155
  2. #2basic_grc1155_token.gno
  3. #3package grc1155 import ( "math/overflow" "gno.land/p/nt/avl/v0" "gno.land/p/nt/ufmt/v0" ) type basicGRC1155Token struct { uri string balances avl.Tree // "TokenId:Address" -> int64 operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool } var _ IGRC1155 = (*basicGRC1155Token)(nil) var _ IGRC1155Reader = (*basicGRC1155Token)(nil) // Returns new basic GRC1155 token func NewBasicGRC1155Token(uri string) *basicGRC1155Token { return &basicGRC1155Token{ uri: uri, balances: avl.Tree{}, operatorApprovals: avl.Tree{}, } } func (s *basicGRC1155Token) Uri() string { return s.uri } // BalanceOf returns the input address's balance of the token type requested func (s *basicGRC1155Token) BalanceOf(addr address, tid TokenID) (int64, error) { if !isValidAddress(addr) { return 0, ErrInvalidAddress } key := string(tid) + ":" + addr.String() balance := s.balances.Get(key) if balance == nil { return 0, nil } return balance.(int64), nil } // BalanceOfBatch returns the balance of multiple account/token pairs func (s *basicGRC1155Token) BalanceOfBatch(owners []address, batch []TokenID) ([]int64, error) { if len(owners) != len(batch) { return nil, ErrMismatchLength } balanceOfBatch := make([]int64, len(owners)) for i := 0; i < len(owners); i++ { balanceOfBatch[i], _ = s.BalanceOf(owners[i], batch[i]) } return balanceOfBatch, nil } // SetApprovalForAll can approve the operator to operate on all tokens func (s *basicGRC1155Token) SetApprovalForAll(caller, operator address, approved bool) error { if !isValidAddress(caller) { return ErrInvalidAddress } if !isValidAddress(operator) { return ErrInvalidAddress } return s.setApprovalForAll(caller, operator, approved) } // IsApprovedForAll returns true if operator is the owner or is approved for all by the owner. // Otherwise, returns false func (s *basicGRC1155Token) IsApprovedForAll(owner, operator address) bool { if operator == owner { return true } key := owner.String() + ":" + operator.String() approved := s.operatorApprovals.Get(key) if approved == nil { return false } ab, ok := approved.(bool) return ok && ab } // Safely transfers `tokenId` token from `from` to `to`, checking that // contract recipients are aware of the GRC1155 protocol to prevent // tokens from being forever locked. func (s *basicGRC1155Token) SafeTransferFrom(caller, from, to address, tid TokenID, amount int64) error { if !isValidAddress(caller) { return ErrInvalidAddress } if !s.IsApprovedForAll(from, caller) { return ErrCallerIsNotOwnerOrApproved } err := s.safeBatchTransferFrom(caller, from, to, []TokenID{tid}, []int64{amount}) if err != nil { return err } if !s.doSafeTransferAcceptanceCheck(caller, from, to, tid, amount) { return ErrTransferToRejectedOrNonGRC1155Receiver } emit(&TransferSingleEvent{caller, from, to, tid, amount}) return nil } // Safely transfers a `batch` of tokens from `from` to `to`, checking that // contract recipients are aware of the GRC1155 protocol to prevent // tokens from being forever locked. func (s *basicGRC1155Token) SafeBatchTransferFrom(caller, from, to address, batch []TokenID, amounts []int64) error { if !isValidAddress(caller) { return ErrInvalidAddress } if !s.IsApprovedForAll(from, caller) { return ErrCallerIsNotOwnerOrApproved } err := s.safeBatchTransferFrom(caller, from, to, batch, amounts) if err != nil { return err } if !s.doSafeBatchTransferAcceptanceCheck(caller, from, to, batch, amounts) { return ErrTransferToRejectedOrNonGRC1155Receiver } emit(&TransferBatchEvent{caller, from, to, batch, amounts}) return nil } // Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that // contract recipients are using GRC1155 protocol. func (s *basicGRC1155Token) SafeMint(caller, to address, tid TokenID, amount int64) error { if !isValidAddress(caller) { return ErrInvalidAddress } err := s.mintBatch(caller, to, []TokenID{tid}, []int64{amount}) if err != nil { return err } if !s.doSafeTransferAcceptanceCheck(caller, zeroAddress, to, tid, amount) { return ErrTransferToRejectedOrNonGRC1155Receiver } emit(&TransferSingleEvent{caller, zeroAddress, to, tid, amount}) return nil } // Batch version of `SafeMint()`. Also checks that // contract recipients are using GRC1155 protocol. func (s *basicGRC1155Token) SafeBatchMint(caller, to address, batch []TokenID, amounts []int64) error { if !isValidAddress(caller) { return ErrInvalidAddress } err := s.mintBatch(caller, to, batch, amounts) if err != nil { return err } if !s.doSafeBatchTransferAcceptanceCheck(caller, zeroAddress, to, batch, amounts) { return ErrTransferToRejectedOrNonGRC1155Receiver } emit(&TransferBatchEvent{caller, zeroAddress, to, batch, amounts}) return nil } // Destroys `amount` tokens of token type `id` from `from`. func (s *basicGRC1155Token) Burn(caller, from address, tid TokenID, amount int64) error { if !isValidAddress(caller) { return ErrInvalidAddress } err := s.burnBatch(caller, from, []TokenID{tid}, []int64{amount}) if err != nil { return err } emit(&TransferSingleEvent{caller, from, zeroAddress, tid, amount}) return nil } // Batch version of `Burn()` func (s *basicGRC1155Token) BatchBurn(caller, from address, batch []TokenID, amounts []int64) error { if !isValidAddress(caller) { return ErrInvalidAddress } err := s.burnBatch(caller, from, batch, amounts) if err != nil { return err } emit(&TransferBatchEvent{caller, from, zeroAddress, batch, amounts}) return nil } /* Helper methods */ // Helper for SetApprovalForAll(): approve `operator` to operate on all of `owner` tokens func (s *basicGRC1155Token) setApprovalForAll(owner, operator address, approved bool) error { if owner == operator { return nil } key := owner.String() + ":" + operator.String() if approved { s.operatorApprovals.Set(key, approved) } else { s.operatorApprovals.Remove(key) } emit(&ApprovalForAllEvent{owner, operator, approved}) return nil } // Helper for SafeTransferFrom() and SafeBatchTransferFrom() func (s *basicGRC1155Token) safeBatchTransferFrom(caller, from, to address, batch []TokenID, amounts []int64) error { if len(batch) != len(amounts) { return ErrMismatchLength } if !isValidAddress(from) || !isValidAddress(to) { return ErrInvalidAddress } if from == to { return ErrCannotTransferToSelf } for _, amount := range amounts { if amount < 0 { return ErrInvalidAmount } } s.beforeTokenTransfer(caller, from, to, batch, amounts) for i := 0; i < len(batch); i++ { tid := batch[i] amount := amounts[i] fromBalance, err := s.BalanceOf(from, tid) if err != nil { return err } if fromBalance < amount { return ErrInsufficientBalance } toBalance, err := s.BalanceOf(to, tid) if err != nil { return err } fromBalance = overflow.Sub64p(fromBalance, amount) toBalance = overflow.Add64p(toBalance, amount) fromBalanceKey := string(tid) + ":" + from.String() toBalanceKey := string(tid) + ":" + to.String() s.balances.Set(fromBalanceKey, fromBalance) s.balances.Set(toBalanceKey, toBalance) } s.afterTokenTransfer(caller, from, to, batch, amounts) return nil } // Helper for SafeMint() and SafeBatchMint() func (s *basicGRC1155Token) mintBatch(caller, to address, batch []TokenID, amounts []int64) error { if len(batch) != len(amounts) { return ErrMismatchLength } if !isValidAddress(to) { return ErrInvalidAddress } for _, amount := range amounts { if amount < 0 { return ErrInvalidAmount } } s.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts) for i := 0; i < len(batch); i++ { tid := batch[i] amount := amounts[i] toBalance, err := s.BalanceOf(to, tid) if err != nil { return err } toBalance = overflow.Add64p(toBalance, amount) toBalanceKey := string(tid) + ":" + to.String() s.balances.Set(toBalanceKey, toBalance) } s.afterTokenTransfer(caller, zeroAddress, to, batch, amounts) return nil } // Helper for Burn() and BurnBatch() func (s *basicGRC1155Token) burnBatch(caller, from address, batch []TokenID, amounts []int64) error { if len(batch) != len(amounts) { return ErrMismatchLength } if !isValidAddress(from) { return ErrInvalidAddress } for _, amount := range amounts { if amount < 0 { return ErrInvalidAmount } } s.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts) for i := 0; i < len(batch); i++ { tid := batch[i] amount := amounts[i] fromBalance, err := s.BalanceOf(from, tid) if err != nil { return err } if fromBalance < amount { return ErrBurnAmountExceedsBalance } fromBalance = overflow.Sub64p(fromBalance, amount) fromBalanceKey := string(tid) + ":" + from.String() s.balances.Set(fromBalanceKey, fromBalance) } s.afterTokenTransfer(caller, from, zeroAddress, batch, amounts) return nil } func (s *basicGRC1155Token) setUri(newUri string) { s.uri = newUri emit(&UpdateURIEvent{newUri}) } func (s *basicGRC1155Token) beforeTokenTransfer(operator, from, to address, batch []TokenID, amounts []int64) { // TODO: Implementation } func (s *basicGRC1155Token) afterTokenTransfer(operator, from, to address, batch []TokenID, amounts []int64) { // TODO: Implementation } func (s *basicGRC1155Token) doSafeTransferAcceptanceCheck(operator, from, to address, tid TokenID, amount int64) bool { // TODO: Implementation return true } func (s *basicGRC1155Token) doSafeBatchTransferAcceptanceCheck(operator, from, to address, batch []TokenID, amounts []int64) bool { // TODO: Implementation return true } func (s *basicGRC1155Token) RenderHome() (str string) { str += ufmt.Sprintf("# URI:%s\n", s.uri) return } func (mt *basicGRC1155Token) Getter() MultiTokenGetter { return func() IGRC1155Reader { return mt } }
  4. #4basic_grc1155_token_test.gno
  5. #5package grc1155 import ( "testing" "gno.land/p/nt/testutils/v0" "gno.land/p/nt/uassert/v0" ) func TestBasicGRC1155GetterReturnsReader(cur realm, t *testing.T) { token := NewBasicGRC1155Token("ipfs://metadata") reader := token.Getter()() uassert.Equal(t, "ipfs://metadata", reader.Uri()) balance, err := reader.BalanceOf(testutils.TestAddress("readerOwner"), TokenID("1")) uassert.True(t, err == nil, "reader balance lookup should not error") uassert.Equal(t, int64(0), balance) uassert.False(t, reader.IsApprovedForAll(testutils.TestAddress("readerOwner"), testutils.TestAddress("readerOperator")), "reader should expose approval reads") }
  6. #6errors.gno
  7. #7package grc1155 import "errors" var ( ErrInvalidAddress = errors.New("invalid address") ErrMismatchLength = errors.New("accounts and ids length mismatch") ErrCannotTransferToSelf = errors.New("cannot send transfer to self") ErrTransferToRejectedOrNonGRC1155Receiver = errors.New("transfer to rejected or non GRC1155Receiver implementer") ErrCallerIsNotOwnerOrApproved = errors.New("caller is not token owner or approved") ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrBurnAmountExceedsBalance = errors.New("burn amount exceeds balance") ErrInvalidAmount = errors.New("invalid amount") )
  8. #8gnomod.toml
  9. #9module = "gno.land/p/akkadia/beta/grc1155" gno = "0.9"
  10. #10igrc1155.gno
  11. #11package grc1155 type IGRC1155 interface { SafeTransferFrom(caller, from, to address, tid TokenID, amount int64) error SafeBatchTransferFrom(caller, from, to address, batch []TokenID, amounts []int64) error BalanceOf(owner address, tid TokenID) (int64, error) BalanceOfBatch(owners []address, batch []TokenID) ([]int64, error) SetApprovalForAll(caller, operator address, approved bool) error IsApprovedForAll(owner, operator address) bool } type IGRC1155Reader interface { Uri() string BalanceOf(owner address, tid TokenID) (int64, error) BalanceOfBatch(owners []address, batch []TokenID) ([]int64, error) IsApprovedForAll(owner, operator address) bool } type TokenID string type TransferSingleEvent struct { Operator address From address To address TokenID TokenID Amount int64 } type TransferBatchEvent struct { Operator address From address To address Batch []TokenID Amounts []int64 } type ApprovalForAllEvent struct { Owner address Operator address Approved bool } type UpdateURIEvent struct { URI string } type MultiTokenGetter func() IGRC1155Reader
  12. #12util.gno
  13. #13package grc1155 const zeroAddress address = "" func isValidAddress(addr address) bool { if !addr.IsValid() { return false } return true } func emit(event any) { // TODO: setup a pubsub system here? }
  14. #14/gno.MemPackageType
  15. #15 MPUserAll

Result log

msg:0,success:true,log:,events:[]