From d97ced4839e77c9c2d0a533aea752adcc5b9291c Mon Sep 17 00:00:00 2001 From: Crystal Lemire Date: Wed, 5 Jun 2024 15:42:38 -0700 Subject: [PATCH] Revert "Evaluate channel READ/WRITE entitlements locally on stream node" (#113) Reverts river-build/river#19 --- core/node/auth/auth_impl.go | 296 ++--- core/node/auth/banning.go | 118 -- core/node/auth/entitlement.go | 52 - core/node/auth/space_contract.go | 15 +- core/node/auth/space_contract_v3.go | 213 +--- core/node/config-template.yaml | 2 - core/node/contracts/base/banning.go | 591 --------- core/node/contracts/base/erc721a_queryable.go | 956 --------------- core/sdk/src/channelsWithEntitlements.test.ts | 592 ++------- core/sdk/src/spaceWithEntitlements.test.ts | 1079 +++++++++++++---- core/sdk/src/util.test.ts | 209 +--- core/web3/src/ConvertersRoles.ts | 2 +- core/web3/src/TestGatingNFT.ts | 3 +- core/web3/src/entitlement.ts | 2 +- core/xchain/entitlement/check_operation.go | 25 +- core/xchain/entitlement/entitlement.go | 3 +- core/xchain/entitlement/linkedwallets.go | 111 -- core/xchain/server/server.go | 100 +- scripts/gen-river-node-bindings.sh | 2 - 19 files changed, 1212 insertions(+), 3159 deletions(-) delete mode 100644 core/node/auth/banning.go delete mode 100644 core/node/contracts/base/banning.go delete mode 100644 core/node/contracts/base/erc721a_queryable.go delete mode 100644 core/xchain/entitlement/linkedwallets.go diff --git a/core/node/auth/auth_impl.go b/core/node/auth/auth_impl.go index 7822b3015a..e90a8a7816 100644 --- a/core/node/auth/auth_impl.go +++ b/core/node/auth/auth_impl.go @@ -164,7 +164,7 @@ func NewChainAuth( metrics infra.MetricsFactory, ) (*chainAuth, error) { // instantiate contract facets from diamond configuration - spaceContract, err := NewSpaceContractV3(ctx, architectCfg, blockchain.Config, blockchain.Client) + spaceContract, err := NewSpaceContractV3(ctx, architectCfg, blockchain.Client) if err != nil { return nil, err } @@ -342,7 +342,7 @@ func (ca *chainAuth) checkChannelEnabled( // not that the caller is allowed to access the permission type entitlementCacheResult struct { allowed bool - entitlementData []Entitlement + entitlementData []SpaceEntitlements owner common.Address } @@ -376,142 +376,90 @@ func (ca *chainAuth) getSpaceEntitlementsForPermissionUncached( return &entitlementCacheResult{allowed: true, entitlementData: entitlementData, owner: owner}, nil } -// If entitlements are found for the permissions, they are returned and the allowed flag is set true so the results may be cached. -// If the call fails or the space is not found, the allowed flag is set to false so the negative caching time applies. -func (ca *chainAuth) getChannelEntitlementsForPermissionUncached( +func deserializeWallets(serialized string) []common.Address { + addressStrings := strings.Split(serialized, ",") + linkedWallets := make([]common.Address, len(addressStrings)) + for i, addrStr := range addressStrings { + linkedWallets[i] = common.HexToAddress(addrStr) + } + return linkedWallets +} + +func (ca *chainAuth) isEntitledToSpaceUncached( ctx context.Context, cfg *config.Config, args *ChainAuthArgs, ) (CacheResult, error) { log := dlog.FromCtx(ctx) - entitlementData, owner, err := ca.spaceContract.GetChannelEntitlementsForPermission( + log.Debug("isEntitledToSpaceUncached", "args", args) + result, cacheHit, err := ca.entitlementManagerCache.executeUsingCache( ctx, - args.spaceId, - args.channelId, - args.permission, + cfg, + args, + ca.getSpaceEntitlementsForPermissionUncached, ) - - log.Debug("getChannelEntitlementsForPermissionUncached", "args", args, "entitlementData", entitlementData) if err != nil { - return &entitlementCacheResult{ + return &boolCacheResult{ allowed: false, }, AsRiverError( err, - ).Func("getChannelEntitlementsForPermission"). - Message("Failed to get channel entitlements") + ).Func("isEntitledToSpace"). + Message("Failed to get space entitlements") } - return &entitlementCacheResult{allowed: true, entitlementData: entitlementData, owner: owner}, nil -} -func (ca *chainAuth) isEntitledToChannelUncached( - ctx context.Context, - cfg *config.Config, - args *ChainAuthArgs, -) (CacheResult, error) { - log := dlog.FromCtx(ctx) - log.Debug("isEntitledToChannelUncached", "args", args) - - // For read and write permissions, fetch the entitlements and evaluate them locally. - if (args.permission == PermissionRead) || (args.permission == PermissionWrite) { - result, cacheHit, err := ca.entitlementManagerCache.executeUsingCache( - ctx, - cfg, - args, - ca.getChannelEntitlementsForPermissionUncached, - ) - if err != nil { - return &boolCacheResult{ - allowed: false, - }, AsRiverError( - err, - ).Func("isEntitledToChannel"). - Message("Failed to get channel entitlements") - } + if cacheHit { + ca.entitlementCacheHit.Inc() + } else { + ca.entitlementCacheMiss.Inc() + } - if cacheHit { - ca.entitlementCacheHit.Inc() - } else { - ca.entitlementCacheMiss.Inc() - } + temp := (result.(*timestampedCacheValue).Result()) - temp := (result.(*timestampedCacheValue).Result()) - entitlementData := temp.(*entitlementCacheResult) // Assuming result is of *entitlementCacheResult type + wallets := deserializeWallets(args.linkedWallets) - allowed, err := ca.evaluateWithEntitlements( - ctx, - cfg, - args, - entitlementData.owner, - entitlementData.entitlementData, - ) - if err != nil { - err = AsRiverError(err). - Func("isEntitledToChannel"). - Message("Failed to evaluate entitlements"). - Tag("channelId", args.channelId) + for _, wallet := range wallets { + if wallet == temp.(*entitlementCacheResult).owner { + log.Debug( + "owner is entitled to space", + "spaceId", + args.spaceId, + "userId", + wallet, + "principal", + args.principal, + ) + return &boolCacheResult{allowed: true}, nil } - return &boolCacheResult{allowed}, err } - // For all other permissions, defer the entitlement check to existing synchronous logic on the space contract. - // This call will ignore cross-chain entitlements. - allowed, err := ca.spaceContract.IsEntitledToChannel( - ctx, - args.spaceId, - args.channelId, - args.principal, - args.permission, - ) - return &boolCacheResult{allowed: allowed}, err -} - -func deserializeWallets(serialized string) []common.Address { - addressStrings := strings.Split(serialized, ",") - linkedWallets := make([]common.Address, len(addressStrings)) - for i, addrStr := range addressStrings { - linkedWallets[i] = common.HexToAddress(addrStr) - } - return linkedWallets -} - -// evaluateEntitlementData evaluates a list of entitlements and returns true if any of them are true. -// The entitlements are evaluated across all linked wallets - if any of the wallets are entitled, the user is entitled. -// Rule entitlements are evaluated by a library shared with xchain and user entitlements are evaluated in the loop. -func (ca *chainAuth) evaluateEntitlementData( - ctx context.Context, - entitlements []Entitlement, - cfg *config.Config, - args *ChainAuthArgs, -) (bool, error) { - log := dlog.FromCtx(ctx).With("function", "evaluateEntitlementData") - log.Debug("evaluateEntitlementData", "args", args) - - wallets := deserializeWallets(args.linkedWallets) - for _, ent := range entitlements { + entitlementData := temp.(*entitlementCacheResult) // Assuming result is of *entitlementCacheResult type + log.Debug("entitlementData", "args", args, "entitlementData", entitlementData) + for _, ent := range entitlementData.entitlementData { + log.Debug("entitlement", "entitlement", ent) if ent.entitlementType == "RuleEntitlement" { re := ent.ruleEntitlement - log.Debug("RuleEntitlement", "ruleEntitlement", re) result, err := ca.evaluator.EvaluateRuleData(ctx, wallets, re) if err != nil { - return false, err + return &boolCacheResult{allowed: false}, AsRiverError(err).Func("isEntitledToSpace") } if result { log.Debug("rule entitlement is true", "spaceId", args.spaceId) - return true, nil + return &boolCacheResult{allowed: true}, nil } else { log.Debug("rule entitlement is false", "spaceId", args.spaceId) + continue } } else if ent.entitlementType == "UserEntitlement" { - log.Debug("UserEntitlement", "userEntitlement", ent.userEntitlement) + log.Debug("UserEntitlement", "userEntitlement", ent) for _, user := range ent.userEntitlement { if user == everyone { - log.Debug("user entitlement: everyone is entitled to space", "spaceId", args.spaceId) - return true, nil + log.Debug("everyone is entitled to space", "spaceId", args.spaceId) + return &boolCacheResult{allowed: true}, nil } else { for _, wallet := range wallets { - if wallet == user { - log.Debug("user entitlement: wallet is entitled to space", "spaceId", args.spaceId, "wallet", wallet) - return true, nil + if user == wallet { + log.Debug("user is entitled to space", "spaceId", args.spaceId, "userId", wallet, "principal", args.principal) + return &boolCacheResult{allowed: true}, nil } } } @@ -520,109 +468,8 @@ func (ca *chainAuth) evaluateEntitlementData( log.Warn("Invalid entitlement type", "entitlement", ent) } } - return false, nil -} -// evaluateWithEntitlements evaluates a user permission considering 3 factors: -// 1. Are they the space owner? The space owner has su over all space operations. -// 2. Are they banned from the space? If so, they are not entitled to anything. -// 3. Are they entitled to the space based on the entitlement data? -func (ca *chainAuth) evaluateWithEntitlements( - ctx context.Context, - cfg *config.Config, - args *ChainAuthArgs, - owner common.Address, - entitlements []Entitlement, -) (bool, error) { - log := dlog.FromCtx(ctx) - - // 1. Check if the user is the space owner - // Space owner has su over all space operations. - log.Info("evaluateWithEntitlements", "args", args, "owner", owner.Hex(), "wallets", args.linkedWallets) - wallets := deserializeWallets(args.linkedWallets) - for _, wallet := range wallets { - if wallet == owner { - log.Debug( - "owner is entitled to space", - "spaceId", - args.spaceId, - "userId", - wallet, - "principal", - args.principal, - ) - return true, nil - } - } - // 2. Check if the user has been banned - banned, err := ca.spaceContract.IsBanned(ctx, args.spaceId, wallets) - if err != nil { - return false, AsRiverError( - err, - ).Func("evaluateEntitlements"). - Tag("spaceId", args.spaceId). - Tag("userId", args.principal) - } - if banned { - log.Warn( - "Evaluating entitlements for a user who is banned from the space", - "userId", - args.principal, - "spaceId", - args.spaceId, - "linkedWallets", - args.linkedWallets, - ) - return false, nil - } - - // 3. Evaluate entitlement data to check if the user is entitled to the space. - allowed, err := ca.evaluateEntitlementData(ctx, entitlements, cfg, args) - if err != nil { - return false, AsRiverError(err).Func("evaluateEntitlements") - } else { - return allowed, nil - } -} - -func (ca *chainAuth) isEntitledToSpaceUncached( - ctx context.Context, - cfg *config.Config, - args *ChainAuthArgs, -) (CacheResult, error) { - log := dlog.FromCtx(ctx) - log.Debug("isEntitledToSpaceUncached", "args", args) - result, cacheHit, err := ca.entitlementManagerCache.executeUsingCache( - ctx, - cfg, - args, - ca.getSpaceEntitlementsForPermissionUncached, - ) - if err != nil { - return &boolCacheResult{ - allowed: false, - }, AsRiverError( - err, - ).Func("isEntitledToSpace"). - Message("Failed to get space entitlements") - } - - if cacheHit { - ca.entitlementCacheHit.Inc() - } else { - ca.entitlementCacheMiss.Inc() - } - - temp := (result.(*timestampedCacheValue).Result()) - entitlementData := temp.(*entitlementCacheResult) // Assuming result is of *entitlementCacheResult type - - allowed, err := ca.evaluateWithEntitlements(ctx, cfg, args, entitlementData.owner, entitlementData.entitlementData) - if err != nil { - err = AsRiverError(err). - Func("isEntitledToSpace"). - Message("Failed to evaluate entitlements") - } - return &boolCacheResult{allowed}, err + return &boolCacheResult{allowed: false}, nil } func (ca *chainAuth) isEntitledToSpace(ctx context.Context, cfg *config.Config, args *ChainAuthArgs) (bool, error) { @@ -643,6 +490,30 @@ func (ca *chainAuth) isEntitledToSpace(ctx context.Context, cfg *config.Config, return isEntitled.IsAllowed(), nil } +func (ca *chainAuth) isEntitledToChannelUncached( + ctx context.Context, + cfg *config.Config, + args *ChainAuthArgs, +) (CacheResult, error) { + wallets := deserializeWallets(args.linkedWallets) + for _, wallet := range wallets { + allowed, err := ca.spaceContract.IsEntitledToChannel( + ctx, + args.spaceId, + args.channelId, + wallet, + args.permission, + ) + if err != nil { + return &boolCacheResult{allowed: false}, err + } + if allowed { + return &boolCacheResult{allowed: true}, nil + } + } + return &boolCacheResult{allowed: false}, nil +} + func (ca *chainAuth) isEntitledToChannel(ctx context.Context, cfg *config.Config, args *ChainAuthArgs) (bool, error) { if args.kind != chainAuthKindChannel { return false, RiverError(Err_INTERNAL, "Wrong chain auth kind") @@ -661,20 +532,23 @@ func (ca *chainAuth) isEntitledToChannel(ctx context.Context, cfg *config.Config return isEntitled.IsAllowed(), nil } -func (ca *chainAuth) getLinkedWallets(ctx context.Context, wallet common.Address) ([]common.Address, error) { +func (ca *chainAuth) getLinkedWallets(ctx context.Context, rootKey common.Address) ([]common.Address, error) { log := dlog.FromCtx(ctx) if ca.walletLinkContract == nil { log.Warn("Wallet link contract is not setup properly, returning root key only") - return []common.Address{wallet}, nil + return []common.Address{rootKey}, nil } - wallets, err := entitlement.GetLinkedWallets(ctx, wallet, ca.walletLinkContract, nil, nil, nil) + // get all the wallets for the root key. + wallets, err := ca.walletLinkContract.GetWalletsByRootKey(ctx, rootKey) if err != nil { - log.Error("Failed to get linked wallets", "err", err, "wallet", wallet.Hex()) + log.Error("error getting all wallets", "rootKey", rootKey.Hex(), "error", err) return nil, err } + log.Debug("allRelevantWallets", "wallets", wallets) + return wallets, nil } @@ -733,6 +607,8 @@ func (ca *chainAuth) checkEntitlement( return &boolCacheResult{allowed: false}, err } + // Add the root key to the list of wallets. + wallets = append(wallets, args.principal) args = args.withLinkedWallets(wallets) isMemberCtx, isMemberCancel := context.WithCancel(ctx) diff --git a/core/node/auth/banning.go b/core/node/auth/banning.go deleted file mode 100644 index 0cbd7fa546..0000000000 --- a/core/node/auth/banning.go +++ /dev/null @@ -1,118 +0,0 @@ -package auth - -import ( - "context" - "sync" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - . "github.com/river-build/river/core/node/base" - "github.com/river-build/river/core/node/config" - baseContracts "github.com/river-build/river/core/node/contracts/base" - . "github.com/river-build/river/core/node/protocol" -) - -type Banning interface { - IsBanned(ctx context.Context, wallets []common.Address) (bool, error) -} - -type bannedAddressCache struct { - mu sync.Mutex - cacheTtl time.Duration - bannedAddresses map[common.Address]struct{} - lastUpdated time.Time -} - -func NewBannedAddressCache(ttl time.Duration) *bannedAddressCache { - return &bannedAddressCache{ - bannedAddresses: map[common.Address]struct{}{}, - lastUpdated: time.Time{}, - cacheTtl: ttl, - } -} - -func (b *bannedAddressCache) IsBanned( - wallets []common.Address, - onMiss func() (map[common.Address]struct{}, error), -) (bool, error) { - b.mu.Lock() - defer b.mu.Unlock() - - zeroTime := time.Time{} - if b.lastUpdated == zeroTime || time.Since(b.lastUpdated) > b.cacheTtl { - bannedAddresses, err := onMiss() - if err != nil { - return false, err - } - - b.bannedAddresses = bannedAddresses - b.lastUpdated = time.Now() - } - - for _, wallet := range wallets { - if _, banned := b.bannedAddresses[wallet]; banned { - return true, nil - } - } - return false, nil -} - -type banning struct { - contract *baseContracts.Banning - tokenContract *baseContracts.Erc721aQueryable - spaceAddress common.Address - - bannedAddressCache *bannedAddressCache -} - -func (b *banning) IsBanned(ctx context.Context, wallets []common.Address) (bool, error) { - return b.bannedAddressCache.IsBanned(wallets, func() (map[common.Address]struct{}, error) { - bannedTokens, err := b.contract.Banned(nil) - if err != nil { - return nil, WrapRiverError(Err_CANNOT_CALL_CONTRACT, err). - Func("IsBanned"). - Message("Failed to get banned token ids") - } - bannedAddresses := map[common.Address]struct{}{} - for _, token := range bannedTokens { - tokenOwnership, err := b.tokenContract.ExplicitOwnershipOf(nil, token) - if err != nil { - return nil, WrapRiverError(Err_CANNOT_CALL_CONTRACT, err). - Func("IsBanned"). - Message("Failed to get owner of banned token") - } - // Ignore burned tokens or any resopnse that indicates a token id out of bounds - zeroAddress := common.Address{} - if !tokenOwnership.Burned && tokenOwnership.Addr != zeroAddress { - bannedAddresses[tokenOwnership.Addr] = struct{}{} - } - } - return bannedAddresses, nil - }) -} - -func NewBanning( - ctx context.Context, - cfg *config.ChainConfig, - version string, - spaceAddress common.Address, - backend bind.ContractBackend, -) (Banning, error) { - contract, err := baseContracts.NewBanning(spaceAddress, backend) - if err != nil { - return nil, err - } - - // Default to 2s - negativeCacheTTL := 2 * time.Second - if cfg.NegativeEntitlementCacheTTLSeconds > 0 { - negativeCacheTTL = time.Duration(cfg.NegativeEntitlementCacheTTLSeconds) * time.Second - } - - return &banning{ - contract: contract, - spaceAddress: spaceAddress, - bannedAddressCache: NewBannedAddressCache(negativeCacheTTL), - }, nil -} diff --git a/core/node/auth/entitlement.go b/core/node/auth/entitlement.go index f0701bb249..2a092c5733 100644 --- a/core/node/auth/entitlement.go +++ b/core/node/auth/entitlement.go @@ -20,11 +20,6 @@ type Entitlements interface { opts *bind.CallOpts, permission string, ) ([]base.IEntitlementDataQueryableBaseEntitlementData, error) - GetChannelEntitlementDataByPermission( - opts *bind.CallOpts, - channelId [32]byte, - permission string, - ) ([]base.IEntitlementDataQueryableBaseEntitlementData, error) } type entitlementsProxy struct { managerContract *base.EntitlementsManager @@ -145,53 +140,6 @@ func (proxy *entitlementsProxy) IsEntitledToSpace( return result, nil } -func (proxy *entitlementsProxy) GetChannelEntitlementDataByPermission( - opts *bind.CallOpts, - channelId [32]byte, - permission string, -) ([]base.IEntitlementDataQueryableBaseEntitlementData, error) { - log := dlog.FromCtx(proxy.ctx) - start := time.Now() - log.Debug( - "GetChannelEntitlementDataByPermissions", - "channelId", - channelId, - "permission", - permission, - "address", - proxy.address, - ) - result, err := proxy.queryContract.GetChannelEntitlementDataByPermission(opts, channelId, permission) - if err != nil { - log.Error( - "GetChannelEntitlementDataByPermissions", - "channelId", - channelId, - "permission", - permission, - "address", - proxy.address, - "error", - err, - ) - return nil, WrapRiverError(Err_CANNOT_CALL_CONTRACT, err) - } - log.Debug( - "GetChannelEntitlementDataByPermissions", - "channelId", - channelId, - "permission", - permission, - "address", - proxy.address, - "result", - result, - "duration", - time.Since(start).Milliseconds(), - ) - return result, nil -} - func (proxy *entitlementsProxy) GetEntitlementDataByPermission( opts *bind.CallOpts, permission string, diff --git a/core/node/auth/space_contract.go b/core/node/auth/space_contract.go index 6958ae5029..fa75263675 100644 --- a/core/node/auth/space_contract.go +++ b/core/node/auth/space_contract.go @@ -8,7 +8,7 @@ import ( "github.com/river-build/river/core/xchain/contracts" ) -type Entitlement struct { +type SpaceEntitlements struct { entitlementType string ruleEntitlement *contracts.IRuleData userEntitlement []common.Address @@ -38,21 +38,10 @@ type SpaceContract interface { ctx context.Context, spaceId shared.StreamId, permission Permission, - ) ([]Entitlement, common.Address, error) - GetChannelEntitlementsForPermission( - ctx context.Context, - spaceId shared.StreamId, - channelId shared.StreamId, - permission Permission, - ) ([]Entitlement, common.Address, error) + ) ([]SpaceEntitlements, common.Address, error) IsMember( ctx context.Context, spaceId shared.StreamId, user common.Address, ) (bool, error) - IsBanned( - ctx context.Context, - spaceId shared.StreamId, - linkedWallets []common.Address, - ) (bool, error) } diff --git a/core/node/auth/space_contract_v3.go b/core/node/auth/space_contract_v3.go index 70186463a8..6f1ce65b5b 100644 --- a/core/node/auth/space_contract_v3.go +++ b/core/node/auth/space_contract_v3.go @@ -8,7 +8,6 @@ import ( "sync" . "github.com/river-build/river/core/node/base" - "github.com/river-build/river/core/node/contracts/base" "github.com/river-build/river/core/node/dlog" . "github.com/river-build/river/core/node/protocol" @@ -27,7 +26,6 @@ import ( type Space struct { address common.Address entitlements Entitlements - banning Banning pausable Pausable channels map[shared.StreamId]Channels channelsLock sync.Mutex @@ -35,7 +33,6 @@ type Space struct { type SpaceContractV3 struct { architect Architect - chainCfg *config.ChainConfig version string backend bind.ContractBackend spaces map[shared.StreamId]*Space @@ -47,7 +44,6 @@ var EMPTY_ADDRESS = common.Address{} func NewSpaceContractV3( ctx context.Context, architectCfg *config.ContractConfig, - chainCfg *config.ChainConfig, backend bind.ContractBackend, // walletLinkingCfg *config.ContractConfig, ) (SpaceContract, error) { @@ -58,7 +54,6 @@ func NewSpaceContractV3( spaceContract := &SpaceContractV3{ architect: architect, - chainCfg: chainCfg, version: architectCfg.Version, backend: backend, spaces: make(map[shared.StreamId]*Space), @@ -121,12 +116,59 @@ func getABI() (abi.ABI, error) { return parsedABI, err } -func (sc *SpaceContractV3) marshalEntitlements( +/** + * GetSpaceEntitlementsForPermission returns the entitlements for the given permission. + * The entitlements are returned as a list of SpaceEntitlements. + * Each SpaceEntitlements object contains the entitlement type and the entitlement data. + * The entitlement data is either a RuleEntitlement or a UserEntitlement. + * The RuleEntitlement contains the rule data. + * The UserEntitlement contains the list of user addresses. + * The owner of the space is also returned. + */ +func (sc *SpaceContractV3) GetSpaceEntitlementsForPermission( ctx context.Context, - entitlementData []base.IEntitlementDataQueryableBaseEntitlementData, -) ([]Entitlement, error) { + spaceId shared.StreamId, + permission Permission, +) ([]SpaceEntitlements, common.Address, error) { log := dlog.FromCtx(ctx) - entitlements := make([]Entitlement, len(entitlementData)) + // get the space entitlements and check if user is entitled. + space, err := sc.getSpace(ctx, spaceId) + if err != nil || space == nil { + log.Warn("Failed to get space", "space_id", spaceId, "error", err) + return nil, EMPTY_ADDRESS, err + } + + spaceAsIerc5313, err := ierc5313.NewIerc5313(space.address, sc.backend) + if err != nil { + log.Warn("Failed to get spaceAsIerc5313", "space_id", spaceId, "error", err) + return nil, EMPTY_ADDRESS, err + } + + owner, err := spaceAsIerc5313.Owner(nil) + if err != nil { + log.Warn("Failed to get owner", "space_id", spaceId, "error", err) + return nil, EMPTY_ADDRESS, err + } + + entitlementData, err := space.entitlements.GetEntitlementDataByPermission( + nil, + permission.String(), + ) + log.Info( + "Got entitlement data", + "err", + err, + "entitlement_data", + entitlementData, + "space_id", + spaceId, + "permission", + permission.String(), + ) + if err != nil { + return nil, EMPTY_ADDRESS, err + } + entitlements := make([]SpaceEntitlements, len(entitlementData)) for i, entitlement := range entitlementData { if entitlement.EntitlementType == "RuleEntitlement" { @@ -136,7 +178,7 @@ func (sc *SpaceContractV3) marshalEntitlements( parsedABI, err := getABI() if err != nil { log.Error("Failed to parse ABI", "error", err) - return nil, err + return nil, EMPTY_ADDRESS, err } var ruleData contracts.IRuleData @@ -188,158 +230,19 @@ func (sc *SpaceContractV3) marshalEntitlements( // Parse the ABI definition parsedABI, err := abi.JSON(strings.NewReader(abiDef)) if err != nil { - return nil, err + return nil, EMPTY_ADDRESS, err } var addresses []common.Address // Unpack the data err = parsedABI.UnpackIntoInterface(&addresses, "getAddresses", entitlement.EntitlementData) if err != nil { - return nil, err + return nil, EMPTY_ADDRESS, err } entitlements[i].userEntitlement = addresses } else { - return nil, RiverError(Err_UNKNOWN, "Invalid entitlement type").Tag("entitlement_type", entitlement.EntitlementType) + return nil, EMPTY_ADDRESS, RiverError(Err_UNKNOWN, "Invalid entitlement type").Tag("entitlement_type", entitlement.EntitlementType) } } - return entitlements, nil -} - -func (sc *SpaceContractV3) IsBanned( - ctx context.Context, - spaceId shared.StreamId, - linkedWallets []common.Address, -) (bool, error) { - log := dlog.FromCtx(ctx).With("function", "SpaceContractV3.IsBanned") - space, err := sc.getSpace(ctx, spaceId) - if err != nil || space == nil { - log.Warn("Failed to get space", "space_id", spaceId, "error", err) - return false, err - } - return space.banning.IsBanned(ctx, linkedWallets) -} - -/** - * GetChannelEntitlementsForPermission returns the entitlements for the given permission for a channel. - * The entitlements are returned as a list of `Entitlement`s. - * Each Entitlement object contains the entitlement type and the entitlement data. - * The entitlement data is either a RuleEntitlement or a UserEntitlement. - * The RuleEntitlement contains the rule data. - * The UserEntitlement contains the list of user addresses. - */ -func (sc *SpaceContractV3) GetChannelEntitlementsForPermission( - ctx context.Context, - spaceId shared.StreamId, - channelId shared.StreamId, - permission Permission, -) ([]Entitlement, common.Address, error) { - log := dlog.FromCtx(ctx) - // get the channel entitlements and check if user is entitled. - space, err := sc.getSpace(ctx, spaceId) - if err != nil || space == nil { - log.Warn("Failed to get space", "space_id", spaceId, "error", err) - return nil, EMPTY_ADDRESS, err - } - - // get owner address - owner has all permissions - spaceAsIerc5313, err := ierc5313.NewIerc5313(space.address, sc.backend) - if err != nil { - log.Warn("Failed to get spaceAsIerc5313", "space_id", spaceId, "error", err) - return nil, EMPTY_ADDRESS, err - } - - owner, err := spaceAsIerc5313.Owner(nil) - if err != nil { - log.Warn("Failed to get owner", "space_id", spaceId, "error", err) - return nil, EMPTY_ADDRESS, err - } - - entitlementData, err := space.entitlements.GetChannelEntitlementDataByPermission( - nil, - channelId, - permission.String(), - ) - log.Info( - "Got channel entitlement data", - "err", - err, - "entitlement_data", - entitlementData, - "space_id", - spaceId, - "channel_id", - channelId, - "permission", - permission.String(), - ) - if err != nil { - return nil, EMPTY_ADDRESS, err - } - - entitlements, err := sc.marshalEntitlements(ctx, entitlementData) - if err != nil { - return nil, EMPTY_ADDRESS, err - } - - return entitlements, owner, nil -} - -/** - * GetSpaceEntitlementsForPermission returns the entitlements for the given permission. - * The entitlements are returned as a list of `Entitlement`s. - * Each Entitlement object contains the entitlement type and the entitlement data. - * The entitlement data is either a RuleEntitlement or a UserEntitlement. - * The RuleEntitlement contains the rule data. - * The UserEntitlement contains the list of user addresses. - * The owner of the space is also returned. - */ -func (sc *SpaceContractV3) GetSpaceEntitlementsForPermission( - ctx context.Context, - spaceId shared.StreamId, - permission Permission, -) ([]Entitlement, common.Address, error) { - log := dlog.FromCtx(ctx) - // get the space entitlements and check if user is entitled. - space, err := sc.getSpace(ctx, spaceId) - if err != nil || space == nil { - log.Warn("Failed to get space", "space_id", spaceId, "error", err) - return nil, EMPTY_ADDRESS, err - } - - spaceAsIerc5313, err := ierc5313.NewIerc5313(space.address, sc.backend) - if err != nil { - log.Warn("Failed to get spaceAsIerc5313", "space_id", spaceId, "error", err) - return nil, EMPTY_ADDRESS, err - } - - owner, err := spaceAsIerc5313.Owner(nil) - if err != nil { - log.Warn("Failed to get owner", "space_id", spaceId, "error", err) - return nil, EMPTY_ADDRESS, err - } - - entitlementData, err := space.entitlements.GetEntitlementDataByPermission( - nil, - permission.String(), - ) - log.Info( - "Got entitlement data", - "err", - err, - "entitlement_data", - entitlementData, - "space_id", - spaceId, - "permission", - permission.String(), - ) - if err != nil { - return nil, EMPTY_ADDRESS, err - } - - entitlements, err := sc.marshalEntitlements(ctx, entitlementData) - if err != nil { - return nil, EMPTY_ADDRESS, err - } log.Info( "Returning entitlements", @@ -416,16 +319,10 @@ func (sc *SpaceContractV3) getSpace(ctx context.Context, spaceId shared.StreamId if err != nil { return nil, err } - banning, err := NewBanning(ctx, sc.chainCfg, sc.version, address, sc.backend) - if err != nil { - return nil, err - } - // cache the space sc.spaces[spaceId] = &Space{ address: address, entitlements: entitlements, - banning: banning, pausable: pausable, channels: make(map[shared.StreamId]Channels), } diff --git a/core/node/config-template.yaml b/core/node/config-template.yaml index 4d0f73092e..c3fa4cda28 100644 --- a/core/node/config-template.yaml +++ b/core/node/config-template.yaml @@ -103,6 +103,4 @@ network: # Debug feature flags. disableBaseChain: -chains: '31337:http://localhost:8545,31338:http://localhost:8546,84532:https://sepolia.base.org,11155111:https://ethereum-sepolia-rpc.publicnode.com' - enableDebugEndpoints: diff --git a/core/node/contracts/base/banning.go b/core/node/contracts/base/banning.go deleted file mode 100644 index 0a651972ce..0000000000 --- a/core/node/contracts/base/banning.go +++ /dev/null @@ -1,591 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package base - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription - _ = abi.ConvertType -) - -// BanningMetaData contains all meta data concerning the Banning contract. -var BanningMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"function\",\"name\":\"ban\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"banned\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isBanned\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unban\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Banned\",\"inputs\":[{\"name\":\"moderator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Unbanned\",\"inputs\":[{\"name\":\"moderator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"Banning__AlreadyBanned\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"Banning__CannotBanSelf\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"Banning__InvalidTokenId\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"Banning__NotBanned\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}]", -} - -// BanningABI is the input ABI used to generate the binding from. -// Deprecated: Use BanningMetaData.ABI instead. -var BanningABI = BanningMetaData.ABI - -// Banning is an auto generated Go binding around an Ethereum contract. -type Banning struct { - BanningCaller // Read-only binding to the contract - BanningTransactor // Write-only binding to the contract - BanningFilterer // Log filterer for contract events -} - -// BanningCaller is an auto generated read-only Go binding around an Ethereum contract. -type BanningCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// BanningTransactor is an auto generated write-only Go binding around an Ethereum contract. -type BanningTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// BanningFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type BanningFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// BanningSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type BanningSession struct { - Contract *Banning // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// BanningCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type BanningCallerSession struct { - Contract *BanningCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// BanningTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type BanningTransactorSession struct { - Contract *BanningTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// BanningRaw is an auto generated low-level Go binding around an Ethereum contract. -type BanningRaw struct { - Contract *Banning // Generic contract binding to access the raw methods on -} - -// BanningCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type BanningCallerRaw struct { - Contract *BanningCaller // Generic read-only contract binding to access the raw methods on -} - -// BanningTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type BanningTransactorRaw struct { - Contract *BanningTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewBanning creates a new instance of Banning, bound to a specific deployed contract. -func NewBanning(address common.Address, backend bind.ContractBackend) (*Banning, error) { - contract, err := bindBanning(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &Banning{BanningCaller: BanningCaller{contract: contract}, BanningTransactor: BanningTransactor{contract: contract}, BanningFilterer: BanningFilterer{contract: contract}}, nil -} - -// NewBanningCaller creates a new read-only instance of Banning, bound to a specific deployed contract. -func NewBanningCaller(address common.Address, caller bind.ContractCaller) (*BanningCaller, error) { - contract, err := bindBanning(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &BanningCaller{contract: contract}, nil -} - -// NewBanningTransactor creates a new write-only instance of Banning, bound to a specific deployed contract. -func NewBanningTransactor(address common.Address, transactor bind.ContractTransactor) (*BanningTransactor, error) { - contract, err := bindBanning(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &BanningTransactor{contract: contract}, nil -} - -// NewBanningFilterer creates a new log filterer instance of Banning, bound to a specific deployed contract. -func NewBanningFilterer(address common.Address, filterer bind.ContractFilterer) (*BanningFilterer, error) { - contract, err := bindBanning(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &BanningFilterer{contract: contract}, nil -} - -// bindBanning binds a generic wrapper to an already deployed contract. -func bindBanning(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := BanningMetaData.GetAbi() - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Banning *BanningRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _Banning.Contract.BanningCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Banning *BanningRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Banning.Contract.BanningTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Banning *BanningRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Banning.Contract.BanningTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Banning *BanningCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _Banning.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Banning *BanningTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Banning.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Banning *BanningTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Banning.Contract.contract.Transact(opts, method, params...) -} - -// Banned is a free data retrieval call binding the contract method 0x158fba8f. -// -// Solidity: function banned() view returns(uint256[]) -func (_Banning *BanningCaller) Banned(opts *bind.CallOpts) ([]*big.Int, error) { - var out []interface{} - err := _Banning.contract.Call(opts, &out, "banned") - - if err != nil { - return *new([]*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new([]*big.Int)).(*[]*big.Int) - - return out0, err - -} - -// Banned is a free data retrieval call binding the contract method 0x158fba8f. -// -// Solidity: function banned() view returns(uint256[]) -func (_Banning *BanningSession) Banned() ([]*big.Int, error) { - return _Banning.Contract.Banned(&_Banning.CallOpts) -} - -// Banned is a free data retrieval call binding the contract method 0x158fba8f. -// -// Solidity: function banned() view returns(uint256[]) -func (_Banning *BanningCallerSession) Banned() ([]*big.Int, error) { - return _Banning.Contract.Banned(&_Banning.CallOpts) -} - -// IsBanned is a free data retrieval call binding the contract method 0xc57a9c56. -// -// Solidity: function isBanned(uint256 tokenId) view returns(bool) -func (_Banning *BanningCaller) IsBanned(opts *bind.CallOpts, tokenId *big.Int) (bool, error) { - var out []interface{} - err := _Banning.contract.Call(opts, &out, "isBanned", tokenId) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// IsBanned is a free data retrieval call binding the contract method 0xc57a9c56. -// -// Solidity: function isBanned(uint256 tokenId) view returns(bool) -func (_Banning *BanningSession) IsBanned(tokenId *big.Int) (bool, error) { - return _Banning.Contract.IsBanned(&_Banning.CallOpts, tokenId) -} - -// IsBanned is a free data retrieval call binding the contract method 0xc57a9c56. -// -// Solidity: function isBanned(uint256 tokenId) view returns(bool) -func (_Banning *BanningCallerSession) IsBanned(tokenId *big.Int) (bool, error) { - return _Banning.Contract.IsBanned(&_Banning.CallOpts, tokenId) -} - -// Ban is a paid mutator transaction binding the contract method 0x6b6ece26. -// -// Solidity: function ban(uint256 tokenId) returns() -func (_Banning *BanningTransactor) Ban(opts *bind.TransactOpts, tokenId *big.Int) (*types.Transaction, error) { - return _Banning.contract.Transact(opts, "ban", tokenId) -} - -// Ban is a paid mutator transaction binding the contract method 0x6b6ece26. -// -// Solidity: function ban(uint256 tokenId) returns() -func (_Banning *BanningSession) Ban(tokenId *big.Int) (*types.Transaction, error) { - return _Banning.Contract.Ban(&_Banning.TransactOpts, tokenId) -} - -// Ban is a paid mutator transaction binding the contract method 0x6b6ece26. -// -// Solidity: function ban(uint256 tokenId) returns() -func (_Banning *BanningTransactorSession) Ban(tokenId *big.Int) (*types.Transaction, error) { - return _Banning.Contract.Ban(&_Banning.TransactOpts, tokenId) -} - -// Unban is a paid mutator transaction binding the contract method 0x1519ff4c. -// -// Solidity: function unban(uint256 tokenId) returns() -func (_Banning *BanningTransactor) Unban(opts *bind.TransactOpts, tokenId *big.Int) (*types.Transaction, error) { - return _Banning.contract.Transact(opts, "unban", tokenId) -} - -// Unban is a paid mutator transaction binding the contract method 0x1519ff4c. -// -// Solidity: function unban(uint256 tokenId) returns() -func (_Banning *BanningSession) Unban(tokenId *big.Int) (*types.Transaction, error) { - return _Banning.Contract.Unban(&_Banning.TransactOpts, tokenId) -} - -// Unban is a paid mutator transaction binding the contract method 0x1519ff4c. -// -// Solidity: function unban(uint256 tokenId) returns() -func (_Banning *BanningTransactorSession) Unban(tokenId *big.Int) (*types.Transaction, error) { - return _Banning.Contract.Unban(&_Banning.TransactOpts, tokenId) -} - -// BanningBannedIterator is returned from FilterBanned and is used to iterate over the raw logs and unpacked data for Banned events raised by the Banning contract. -type BanningBannedIterator struct { - Event *BanningBanned // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *BanningBannedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(BanningBanned) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(BanningBanned) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *BanningBannedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *BanningBannedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// BanningBanned represents a Banned event raised by the Banning contract. -type BanningBanned struct { - Moderator common.Address - TokenId *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterBanned is a free log retrieval operation binding the contract event 0x8f9d2f181f599e221d5959b9acbebb1f42c8146251755fd61fc0de85f5d97162. -// -// Solidity: event Banned(address indexed moderator, uint256 indexed tokenId) -func (_Banning *BanningFilterer) FilterBanned(opts *bind.FilterOpts, moderator []common.Address, tokenId []*big.Int) (*BanningBannedIterator, error) { - - var moderatorRule []interface{} - for _, moderatorItem := range moderator { - moderatorRule = append(moderatorRule, moderatorItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Banning.contract.FilterLogs(opts, "Banned", moderatorRule, tokenIdRule) - if err != nil { - return nil, err - } - return &BanningBannedIterator{contract: _Banning.contract, event: "Banned", logs: logs, sub: sub}, nil -} - -// WatchBanned is a free log subscription operation binding the contract event 0x8f9d2f181f599e221d5959b9acbebb1f42c8146251755fd61fc0de85f5d97162. -// -// Solidity: event Banned(address indexed moderator, uint256 indexed tokenId) -func (_Banning *BanningFilterer) WatchBanned(opts *bind.WatchOpts, sink chan<- *BanningBanned, moderator []common.Address, tokenId []*big.Int) (event.Subscription, error) { - - var moderatorRule []interface{} - for _, moderatorItem := range moderator { - moderatorRule = append(moderatorRule, moderatorItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Banning.contract.WatchLogs(opts, "Banned", moderatorRule, tokenIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(BanningBanned) - if err := _Banning.contract.UnpackLog(event, "Banned", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseBanned is a log parse operation binding the contract event 0x8f9d2f181f599e221d5959b9acbebb1f42c8146251755fd61fc0de85f5d97162. -// -// Solidity: event Banned(address indexed moderator, uint256 indexed tokenId) -func (_Banning *BanningFilterer) ParseBanned(log types.Log) (*BanningBanned, error) { - event := new(BanningBanned) - if err := _Banning.contract.UnpackLog(event, "Banned", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// BanningUnbannedIterator is returned from FilterUnbanned and is used to iterate over the raw logs and unpacked data for Unbanned events raised by the Banning contract. -type BanningUnbannedIterator struct { - Event *BanningUnbanned // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *BanningUnbannedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(BanningUnbanned) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(BanningUnbanned) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *BanningUnbannedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *BanningUnbannedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// BanningUnbanned represents a Unbanned event raised by the Banning contract. -type BanningUnbanned struct { - Moderator common.Address - TokenId *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterUnbanned is a free log retrieval operation binding the contract event 0xf46dc693169fba0f08556bb54c8abc995b37535f1c2322598f0e671982d8ff86. -// -// Solidity: event Unbanned(address indexed moderator, uint256 indexed tokenId) -func (_Banning *BanningFilterer) FilterUnbanned(opts *bind.FilterOpts, moderator []common.Address, tokenId []*big.Int) (*BanningUnbannedIterator, error) { - - var moderatorRule []interface{} - for _, moderatorItem := range moderator { - moderatorRule = append(moderatorRule, moderatorItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Banning.contract.FilterLogs(opts, "Unbanned", moderatorRule, tokenIdRule) - if err != nil { - return nil, err - } - return &BanningUnbannedIterator{contract: _Banning.contract, event: "Unbanned", logs: logs, sub: sub}, nil -} - -// WatchUnbanned is a free log subscription operation binding the contract event 0xf46dc693169fba0f08556bb54c8abc995b37535f1c2322598f0e671982d8ff86. -// -// Solidity: event Unbanned(address indexed moderator, uint256 indexed tokenId) -func (_Banning *BanningFilterer) WatchUnbanned(opts *bind.WatchOpts, sink chan<- *BanningUnbanned, moderator []common.Address, tokenId []*big.Int) (event.Subscription, error) { - - var moderatorRule []interface{} - for _, moderatorItem := range moderator { - moderatorRule = append(moderatorRule, moderatorItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Banning.contract.WatchLogs(opts, "Unbanned", moderatorRule, tokenIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(BanningUnbanned) - if err := _Banning.contract.UnpackLog(event, "Unbanned", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseUnbanned is a log parse operation binding the contract event 0xf46dc693169fba0f08556bb54c8abc995b37535f1c2322598f0e671982d8ff86. -// -// Solidity: event Unbanned(address indexed moderator, uint256 indexed tokenId) -func (_Banning *BanningFilterer) ParseUnbanned(log types.Log) (*BanningUnbanned, error) { - event := new(BanningUnbanned) - if err := _Banning.contract.UnpackLog(event, "Unbanned", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/core/node/contracts/base/erc721a_queryable.go b/core/node/contracts/base/erc721a_queryable.go deleted file mode 100644 index 79506e5021..0000000000 --- a/core/node/contracts/base/erc721a_queryable.go +++ /dev/null @@ -1,956 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package base - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription - _ = abi.ConvertType -) - -// IERC721ABaseTokenOwnership is an auto generated low-level Go binding around an user-defined struct. -type IERC721ABaseTokenOwnership struct { - Addr common.Address - StartTimestamp uint64 - Burned bool - ExtraData *big.Int -} - -// Erc721aQueryableMetaData contains all meta data concerning the Erc721aQueryable contract. -var Erc721aQueryableMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"function\",\"name\":\"explicitOwnershipOf\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIERC721ABase.TokenOwnership\",\"components\":[{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"startTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"burned\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"extraData\",\"type\":\"uint24\",\"internalType\":\"uint24\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"explicitOwnershipsOf\",\"inputs\":[{\"name\":\"tokenIds\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple[]\",\"internalType\":\"structIERC721ABase.TokenOwnership[]\",\"components\":[{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"startTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"burned\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"extraData\",\"type\":\"uint24\",\"internalType\":\"uint24\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"tokensOfOwner\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"tokensOfOwnerIn\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"start\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"stop\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"Approval\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"approved\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ApprovalForAll\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"operator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"approved\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ConsecutiveTransfer\",\"inputs\":[{\"name\":\"fromTokenId\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"toTokenId\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Transfer\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"ApprovalCallerNotOwnerNorApproved\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ApprovalQueryForNonexistentToken\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"BalanceQueryForZeroAddress\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidQueryRange\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MintERC2309QuantityExceedsLimit\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MintToZeroAddress\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MintZeroQuantity\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"OwnerQueryForNonexistentToken\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"OwnershipNotInitializedForExtraData\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"TransferCallerNotOwnerNorApproved\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"TransferFromIncorrectOwner\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"TransferToNonERC721ReceiverImplementer\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"TransferToZeroAddress\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"URIQueryForNonexistentToken\",\"inputs\":[]}]", -} - -// Erc721aQueryableABI is the input ABI used to generate the binding from. -// Deprecated: Use Erc721aQueryableMetaData.ABI instead. -var Erc721aQueryableABI = Erc721aQueryableMetaData.ABI - -// Erc721aQueryable is an auto generated Go binding around an Ethereum contract. -type Erc721aQueryable struct { - Erc721aQueryableCaller // Read-only binding to the contract - Erc721aQueryableTransactor // Write-only binding to the contract - Erc721aQueryableFilterer // Log filterer for contract events -} - -// Erc721aQueryableCaller is an auto generated read-only Go binding around an Ethereum contract. -type Erc721aQueryableCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// Erc721aQueryableTransactor is an auto generated write-only Go binding around an Ethereum contract. -type Erc721aQueryableTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// Erc721aQueryableFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type Erc721aQueryableFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// Erc721aQueryableSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type Erc721aQueryableSession struct { - Contract *Erc721aQueryable // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// Erc721aQueryableCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type Erc721aQueryableCallerSession struct { - Contract *Erc721aQueryableCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// Erc721aQueryableTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type Erc721aQueryableTransactorSession struct { - Contract *Erc721aQueryableTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// Erc721aQueryableRaw is an auto generated low-level Go binding around an Ethereum contract. -type Erc721aQueryableRaw struct { - Contract *Erc721aQueryable // Generic contract binding to access the raw methods on -} - -// Erc721aQueryableCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type Erc721aQueryableCallerRaw struct { - Contract *Erc721aQueryableCaller // Generic read-only contract binding to access the raw methods on -} - -// Erc721aQueryableTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type Erc721aQueryableTransactorRaw struct { - Contract *Erc721aQueryableTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewErc721aQueryable creates a new instance of Erc721aQueryable, bound to a specific deployed contract. -func NewErc721aQueryable(address common.Address, backend bind.ContractBackend) (*Erc721aQueryable, error) { - contract, err := bindErc721aQueryable(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &Erc721aQueryable{Erc721aQueryableCaller: Erc721aQueryableCaller{contract: contract}, Erc721aQueryableTransactor: Erc721aQueryableTransactor{contract: contract}, Erc721aQueryableFilterer: Erc721aQueryableFilterer{contract: contract}}, nil -} - -// NewErc721aQueryableCaller creates a new read-only instance of Erc721aQueryable, bound to a specific deployed contract. -func NewErc721aQueryableCaller(address common.Address, caller bind.ContractCaller) (*Erc721aQueryableCaller, error) { - contract, err := bindErc721aQueryable(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &Erc721aQueryableCaller{contract: contract}, nil -} - -// NewErc721aQueryableTransactor creates a new write-only instance of Erc721aQueryable, bound to a specific deployed contract. -func NewErc721aQueryableTransactor(address common.Address, transactor bind.ContractTransactor) (*Erc721aQueryableTransactor, error) { - contract, err := bindErc721aQueryable(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &Erc721aQueryableTransactor{contract: contract}, nil -} - -// NewErc721aQueryableFilterer creates a new log filterer instance of Erc721aQueryable, bound to a specific deployed contract. -func NewErc721aQueryableFilterer(address common.Address, filterer bind.ContractFilterer) (*Erc721aQueryableFilterer, error) { - contract, err := bindErc721aQueryable(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &Erc721aQueryableFilterer{contract: contract}, nil -} - -// bindErc721aQueryable binds a generic wrapper to an already deployed contract. -func bindErc721aQueryable(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := Erc721aQueryableMetaData.GetAbi() - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Erc721aQueryable *Erc721aQueryableRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _Erc721aQueryable.Contract.Erc721aQueryableCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Erc721aQueryable *Erc721aQueryableRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Erc721aQueryable.Contract.Erc721aQueryableTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Erc721aQueryable *Erc721aQueryableRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Erc721aQueryable.Contract.Erc721aQueryableTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Erc721aQueryable *Erc721aQueryableCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _Erc721aQueryable.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Erc721aQueryable *Erc721aQueryableTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Erc721aQueryable.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Erc721aQueryable *Erc721aQueryableTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Erc721aQueryable.Contract.contract.Transact(opts, method, params...) -} - -// ExplicitOwnershipOf is a free data retrieval call binding the contract method 0xc23dc68f. -// -// Solidity: function explicitOwnershipOf(uint256 tokenId) view returns((address,uint64,bool,uint24)) -func (_Erc721aQueryable *Erc721aQueryableCaller) ExplicitOwnershipOf(opts *bind.CallOpts, tokenId *big.Int) (IERC721ABaseTokenOwnership, error) { - var out []interface{} - err := _Erc721aQueryable.contract.Call(opts, &out, "explicitOwnershipOf", tokenId) - - if err != nil { - return *new(IERC721ABaseTokenOwnership), err - } - - out0 := *abi.ConvertType(out[0], new(IERC721ABaseTokenOwnership)).(*IERC721ABaseTokenOwnership) - - return out0, err - -} - -// ExplicitOwnershipOf is a free data retrieval call binding the contract method 0xc23dc68f. -// -// Solidity: function explicitOwnershipOf(uint256 tokenId) view returns((address,uint64,bool,uint24)) -func (_Erc721aQueryable *Erc721aQueryableSession) ExplicitOwnershipOf(tokenId *big.Int) (IERC721ABaseTokenOwnership, error) { - return _Erc721aQueryable.Contract.ExplicitOwnershipOf(&_Erc721aQueryable.CallOpts, tokenId) -} - -// ExplicitOwnershipOf is a free data retrieval call binding the contract method 0xc23dc68f. -// -// Solidity: function explicitOwnershipOf(uint256 tokenId) view returns((address,uint64,bool,uint24)) -func (_Erc721aQueryable *Erc721aQueryableCallerSession) ExplicitOwnershipOf(tokenId *big.Int) (IERC721ABaseTokenOwnership, error) { - return _Erc721aQueryable.Contract.ExplicitOwnershipOf(&_Erc721aQueryable.CallOpts, tokenId) -} - -// ExplicitOwnershipsOf is a free data retrieval call binding the contract method 0x5bbb2177. -// -// Solidity: function explicitOwnershipsOf(uint256[] tokenIds) view returns((address,uint64,bool,uint24)[]) -func (_Erc721aQueryable *Erc721aQueryableCaller) ExplicitOwnershipsOf(opts *bind.CallOpts, tokenIds []*big.Int) ([]IERC721ABaseTokenOwnership, error) { - var out []interface{} - err := _Erc721aQueryable.contract.Call(opts, &out, "explicitOwnershipsOf", tokenIds) - - if err != nil { - return *new([]IERC721ABaseTokenOwnership), err - } - - out0 := *abi.ConvertType(out[0], new([]IERC721ABaseTokenOwnership)).(*[]IERC721ABaseTokenOwnership) - - return out0, err - -} - -// ExplicitOwnershipsOf is a free data retrieval call binding the contract method 0x5bbb2177. -// -// Solidity: function explicitOwnershipsOf(uint256[] tokenIds) view returns((address,uint64,bool,uint24)[]) -func (_Erc721aQueryable *Erc721aQueryableSession) ExplicitOwnershipsOf(tokenIds []*big.Int) ([]IERC721ABaseTokenOwnership, error) { - return _Erc721aQueryable.Contract.ExplicitOwnershipsOf(&_Erc721aQueryable.CallOpts, tokenIds) -} - -// ExplicitOwnershipsOf is a free data retrieval call binding the contract method 0x5bbb2177. -// -// Solidity: function explicitOwnershipsOf(uint256[] tokenIds) view returns((address,uint64,bool,uint24)[]) -func (_Erc721aQueryable *Erc721aQueryableCallerSession) ExplicitOwnershipsOf(tokenIds []*big.Int) ([]IERC721ABaseTokenOwnership, error) { - return _Erc721aQueryable.Contract.ExplicitOwnershipsOf(&_Erc721aQueryable.CallOpts, tokenIds) -} - -// TokensOfOwner is a free data retrieval call binding the contract method 0x8462151c. -// -// Solidity: function tokensOfOwner(address owner) view returns(uint256[]) -func (_Erc721aQueryable *Erc721aQueryableCaller) TokensOfOwner(opts *bind.CallOpts, owner common.Address) ([]*big.Int, error) { - var out []interface{} - err := _Erc721aQueryable.contract.Call(opts, &out, "tokensOfOwner", owner) - - if err != nil { - return *new([]*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new([]*big.Int)).(*[]*big.Int) - - return out0, err - -} - -// TokensOfOwner is a free data retrieval call binding the contract method 0x8462151c. -// -// Solidity: function tokensOfOwner(address owner) view returns(uint256[]) -func (_Erc721aQueryable *Erc721aQueryableSession) TokensOfOwner(owner common.Address) ([]*big.Int, error) { - return _Erc721aQueryable.Contract.TokensOfOwner(&_Erc721aQueryable.CallOpts, owner) -} - -// TokensOfOwner is a free data retrieval call binding the contract method 0x8462151c. -// -// Solidity: function tokensOfOwner(address owner) view returns(uint256[]) -func (_Erc721aQueryable *Erc721aQueryableCallerSession) TokensOfOwner(owner common.Address) ([]*big.Int, error) { - return _Erc721aQueryable.Contract.TokensOfOwner(&_Erc721aQueryable.CallOpts, owner) -} - -// TokensOfOwnerIn is a free data retrieval call binding the contract method 0x99a2557a. -// -// Solidity: function tokensOfOwnerIn(address owner, uint256 start, uint256 stop) view returns(uint256[]) -func (_Erc721aQueryable *Erc721aQueryableCaller) TokensOfOwnerIn(opts *bind.CallOpts, owner common.Address, start *big.Int, stop *big.Int) ([]*big.Int, error) { - var out []interface{} - err := _Erc721aQueryable.contract.Call(opts, &out, "tokensOfOwnerIn", owner, start, stop) - - if err != nil { - return *new([]*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new([]*big.Int)).(*[]*big.Int) - - return out0, err - -} - -// TokensOfOwnerIn is a free data retrieval call binding the contract method 0x99a2557a. -// -// Solidity: function tokensOfOwnerIn(address owner, uint256 start, uint256 stop) view returns(uint256[]) -func (_Erc721aQueryable *Erc721aQueryableSession) TokensOfOwnerIn(owner common.Address, start *big.Int, stop *big.Int) ([]*big.Int, error) { - return _Erc721aQueryable.Contract.TokensOfOwnerIn(&_Erc721aQueryable.CallOpts, owner, start, stop) -} - -// TokensOfOwnerIn is a free data retrieval call binding the contract method 0x99a2557a. -// -// Solidity: function tokensOfOwnerIn(address owner, uint256 start, uint256 stop) view returns(uint256[]) -func (_Erc721aQueryable *Erc721aQueryableCallerSession) TokensOfOwnerIn(owner common.Address, start *big.Int, stop *big.Int) ([]*big.Int, error) { - return _Erc721aQueryable.Contract.TokensOfOwnerIn(&_Erc721aQueryable.CallOpts, owner, start, stop) -} - -// Erc721aQueryableApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the Erc721aQueryable contract. -type Erc721aQueryableApprovalIterator struct { - Event *Erc721aQueryableApproval // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *Erc721aQueryableApprovalIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(Erc721aQueryableApproval) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(Erc721aQueryableApproval) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *Erc721aQueryableApprovalIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *Erc721aQueryableApprovalIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// Erc721aQueryableApproval represents a Approval event raised by the Erc721aQueryable contract. -type Erc721aQueryableApproval struct { - Owner common.Address - Approved common.Address - TokenId *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId) -func (_Erc721aQueryable *Erc721aQueryableFilterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, approved []common.Address, tokenId []*big.Int) (*Erc721aQueryableApprovalIterator, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var approvedRule []interface{} - for _, approvedItem := range approved { - approvedRule = append(approvedRule, approvedItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Erc721aQueryable.contract.FilterLogs(opts, "Approval", ownerRule, approvedRule, tokenIdRule) - if err != nil { - return nil, err - } - return &Erc721aQueryableApprovalIterator{contract: _Erc721aQueryable.contract, event: "Approval", logs: logs, sub: sub}, nil -} - -// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId) -func (_Erc721aQueryable *Erc721aQueryableFilterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *Erc721aQueryableApproval, owner []common.Address, approved []common.Address, tokenId []*big.Int) (event.Subscription, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var approvedRule []interface{} - for _, approvedItem := range approved { - approvedRule = append(approvedRule, approvedItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Erc721aQueryable.contract.WatchLogs(opts, "Approval", ownerRule, approvedRule, tokenIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(Erc721aQueryableApproval) - if err := _Erc721aQueryable.contract.UnpackLog(event, "Approval", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId) -func (_Erc721aQueryable *Erc721aQueryableFilterer) ParseApproval(log types.Log) (*Erc721aQueryableApproval, error) { - event := new(Erc721aQueryableApproval) - if err := _Erc721aQueryable.contract.UnpackLog(event, "Approval", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// Erc721aQueryableApprovalForAllIterator is returned from FilterApprovalForAll and is used to iterate over the raw logs and unpacked data for ApprovalForAll events raised by the Erc721aQueryable contract. -type Erc721aQueryableApprovalForAllIterator struct { - Event *Erc721aQueryableApprovalForAll // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *Erc721aQueryableApprovalForAllIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(Erc721aQueryableApprovalForAll) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(Erc721aQueryableApprovalForAll) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *Erc721aQueryableApprovalForAllIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *Erc721aQueryableApprovalForAllIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// Erc721aQueryableApprovalForAll represents a ApprovalForAll event raised by the Erc721aQueryable contract. -type Erc721aQueryableApprovalForAll struct { - Owner common.Address - Operator common.Address - Approved bool - Raw types.Log // Blockchain specific contextual infos -} - -// FilterApprovalForAll is a free log retrieval operation binding the contract event 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31. -// -// Solidity: event ApprovalForAll(address indexed owner, address indexed operator, bool approved) -func (_Erc721aQueryable *Erc721aQueryableFilterer) FilterApprovalForAll(opts *bind.FilterOpts, owner []common.Address, operator []common.Address) (*Erc721aQueryableApprovalForAllIterator, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var operatorRule []interface{} - for _, operatorItem := range operator { - operatorRule = append(operatorRule, operatorItem) - } - - logs, sub, err := _Erc721aQueryable.contract.FilterLogs(opts, "ApprovalForAll", ownerRule, operatorRule) - if err != nil { - return nil, err - } - return &Erc721aQueryableApprovalForAllIterator{contract: _Erc721aQueryable.contract, event: "ApprovalForAll", logs: logs, sub: sub}, nil -} - -// WatchApprovalForAll is a free log subscription operation binding the contract event 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31. -// -// Solidity: event ApprovalForAll(address indexed owner, address indexed operator, bool approved) -func (_Erc721aQueryable *Erc721aQueryableFilterer) WatchApprovalForAll(opts *bind.WatchOpts, sink chan<- *Erc721aQueryableApprovalForAll, owner []common.Address, operator []common.Address) (event.Subscription, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var operatorRule []interface{} - for _, operatorItem := range operator { - operatorRule = append(operatorRule, operatorItem) - } - - logs, sub, err := _Erc721aQueryable.contract.WatchLogs(opts, "ApprovalForAll", ownerRule, operatorRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(Erc721aQueryableApprovalForAll) - if err := _Erc721aQueryable.contract.UnpackLog(event, "ApprovalForAll", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseApprovalForAll is a log parse operation binding the contract event 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31. -// -// Solidity: event ApprovalForAll(address indexed owner, address indexed operator, bool approved) -func (_Erc721aQueryable *Erc721aQueryableFilterer) ParseApprovalForAll(log types.Log) (*Erc721aQueryableApprovalForAll, error) { - event := new(Erc721aQueryableApprovalForAll) - if err := _Erc721aQueryable.contract.UnpackLog(event, "ApprovalForAll", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// Erc721aQueryableConsecutiveTransferIterator is returned from FilterConsecutiveTransfer and is used to iterate over the raw logs and unpacked data for ConsecutiveTransfer events raised by the Erc721aQueryable contract. -type Erc721aQueryableConsecutiveTransferIterator struct { - Event *Erc721aQueryableConsecutiveTransfer // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *Erc721aQueryableConsecutiveTransferIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(Erc721aQueryableConsecutiveTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(Erc721aQueryableConsecutiveTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *Erc721aQueryableConsecutiveTransferIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *Erc721aQueryableConsecutiveTransferIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// Erc721aQueryableConsecutiveTransfer represents a ConsecutiveTransfer event raised by the Erc721aQueryable contract. -type Erc721aQueryableConsecutiveTransfer struct { - FromTokenId *big.Int - ToTokenId *big.Int - From common.Address - To common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterConsecutiveTransfer is a free log retrieval operation binding the contract event 0xdeaa91b6123d068f5821d0fb0678463d1a8a6079fe8af5de3ce5e896dcf9133d. -// -// Solidity: event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to) -func (_Erc721aQueryable *Erc721aQueryableFilterer) FilterConsecutiveTransfer(opts *bind.FilterOpts, fromTokenId []*big.Int, from []common.Address, to []common.Address) (*Erc721aQueryableConsecutiveTransferIterator, error) { - - var fromTokenIdRule []interface{} - for _, fromTokenIdItem := range fromTokenId { - fromTokenIdRule = append(fromTokenIdRule, fromTokenIdItem) - } - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _Erc721aQueryable.contract.FilterLogs(opts, "ConsecutiveTransfer", fromTokenIdRule, fromRule, toRule) - if err != nil { - return nil, err - } - return &Erc721aQueryableConsecutiveTransferIterator{contract: _Erc721aQueryable.contract, event: "ConsecutiveTransfer", logs: logs, sub: sub}, nil -} - -// WatchConsecutiveTransfer is a free log subscription operation binding the contract event 0xdeaa91b6123d068f5821d0fb0678463d1a8a6079fe8af5de3ce5e896dcf9133d. -// -// Solidity: event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to) -func (_Erc721aQueryable *Erc721aQueryableFilterer) WatchConsecutiveTransfer(opts *bind.WatchOpts, sink chan<- *Erc721aQueryableConsecutiveTransfer, fromTokenId []*big.Int, from []common.Address, to []common.Address) (event.Subscription, error) { - - var fromTokenIdRule []interface{} - for _, fromTokenIdItem := range fromTokenId { - fromTokenIdRule = append(fromTokenIdRule, fromTokenIdItem) - } - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _Erc721aQueryable.contract.WatchLogs(opts, "ConsecutiveTransfer", fromTokenIdRule, fromRule, toRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(Erc721aQueryableConsecutiveTransfer) - if err := _Erc721aQueryable.contract.UnpackLog(event, "ConsecutiveTransfer", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseConsecutiveTransfer is a log parse operation binding the contract event 0xdeaa91b6123d068f5821d0fb0678463d1a8a6079fe8af5de3ce5e896dcf9133d. -// -// Solidity: event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to) -func (_Erc721aQueryable *Erc721aQueryableFilterer) ParseConsecutiveTransfer(log types.Log) (*Erc721aQueryableConsecutiveTransfer, error) { - event := new(Erc721aQueryableConsecutiveTransfer) - if err := _Erc721aQueryable.contract.UnpackLog(event, "ConsecutiveTransfer", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// Erc721aQueryableTransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the Erc721aQueryable contract. -type Erc721aQueryableTransferIterator struct { - Event *Erc721aQueryableTransfer // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *Erc721aQueryableTransferIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(Erc721aQueryableTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(Erc721aQueryableTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *Erc721aQueryableTransferIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *Erc721aQueryableTransferIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// Erc721aQueryableTransfer represents a Transfer event raised by the Erc721aQueryable contract. -type Erc721aQueryableTransfer struct { - From common.Address - To common.Address - TokenId *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) -func (_Erc721aQueryable *Erc721aQueryableFilterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address, tokenId []*big.Int) (*Erc721aQueryableTransferIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Erc721aQueryable.contract.FilterLogs(opts, "Transfer", fromRule, toRule, tokenIdRule) - if err != nil { - return nil, err - } - return &Erc721aQueryableTransferIterator{contract: _Erc721aQueryable.contract, event: "Transfer", logs: logs, sub: sub}, nil -} - -// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) -func (_Erc721aQueryable *Erc721aQueryableFilterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *Erc721aQueryableTransfer, from []common.Address, to []common.Address, tokenId []*big.Int) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Erc721aQueryable.contract.WatchLogs(opts, "Transfer", fromRule, toRule, tokenIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(Erc721aQueryableTransfer) - if err := _Erc721aQueryable.contract.UnpackLog(event, "Transfer", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) -func (_Erc721aQueryable *Erc721aQueryableFilterer) ParseTransfer(log types.Log) (*Erc721aQueryableTransfer, error) { - event := new(Erc721aQueryableTransfer) - if err := _Erc721aQueryable.contract.UnpackLog(event, "Transfer", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/core/sdk/src/channelsWithEntitlements.test.ts b/core/sdk/src/channelsWithEntitlements.test.ts index 113f831317..973e8b05f0 100644 --- a/core/sdk/src/channelsWithEntitlements.test.ts +++ b/core/sdk/src/channelsWithEntitlements.test.ts @@ -5,490 +5,160 @@ import { getChannelMessagePayload, + getDynamicPricingModule, + makeDonePromise, + makeTestClient, + makeUserContextFromWallet, waitFor, - getNftRuleData, - twoNftRuleData, - createRole, - createChannel, - setupWalletsAndContexts, - createSpaceAndDefaultChannel, - expectUserCanJoin, - everyoneMembershipStruct, - linkWallets, } from './util.test' -import { MembershipOp } from '@river-build/proto' -import { makeUserStreamId } from './id' import { dlog } from '@river-build/dlog' +import { makeDefaultChannelStreamId, makeSpaceStreamId, makeUserStreamId } from './id' +import { MembershipOp } from '@river-build/proto' +import { ethers } from 'ethers' import { + ETH_ADDRESS, + LocalhostWeb3Provider, + MembershipStruct, NoopRuleData, - IRuleEntitlement, Permission, - getContractAddress, - publicMint, - LogicalOperationType, - OperationType, - Operation, - CheckOperationType, - treeToRuleData, + createSpaceDapp, } from '@river-build/web3' -import { Client } from './client' +import { makeBaseChainConfig } from './riverConfig' const log = dlog('csb:test:channelsWithEntitlements') -// pass in users as 'alice', 'bob', 'carol' - b/c their wallets are created here -async function setupChannelWithCustomRole( - userNames: string[], - ruleData: IRuleEntitlement.RuleDataStruct, -) { - const { - alice, - bob, - alicesWallet, - bobsWallet, - carolsWallet, - aliceProvider, - bobProvider, - carolProvider, - aliceSpaceDapp, - bobSpaceDapp, - carolSpaceDapp, - } = await setupWalletsAndContexts() - - const userNameToWallet: Record = { - alice: alicesWallet.address, - bob: bobsWallet.address, - carol: carolsWallet.address, - } - const users = userNames.map((user) => userNameToWallet[user]) - - const { spaceId, defaultChannelId } = await createSpaceAndDefaultChannel( - bob, - bobSpaceDapp, - bobProvider.wallet, - 'bob', - await everyoneMembershipStruct(bobSpaceDapp, bob), - ) - - const { roleId, error: roleError } = await createRole( - bobSpaceDapp, - bobProvider, - spaceId, - 'nft-gated read role', - [Permission.Read], - users, - ruleData, - bobProvider.wallet, - ) - expect(roleError).toBeUndefined() - log('roleId', roleId) - - // Create a channel gated by the above role in the space contract. - const { channelId, error: channelError } = await createChannel( - bobSpaceDapp, - bobProvider, - spaceId, - 'custom-role-gated-channel', - [roleId!.valueOf()], - bobProvider.wallet, - ) - expect(channelError).toBeUndefined() - log('channelId', channelId) - - // Then, establish a stream for the channel on the river node. - const { streamId: channelStreamId } = await bob.createChannel( - spaceId, - 'nft-gated-channel', - 'talk about nfts here', - channelId!, - ) - expect(channelStreamId).toEqual(channelId) - // As the space owner, Bob should always be able to join the channel regardless of the custom role. - await expect(bob.joinStream(channelId!)).toResolve() - - // Join alice to the town so she can attempt to join the role-gated channel. - // Alice should have no issue joining the space and default channel for an "everyone" towne. - await expectUserCanJoin( - spaceId, - defaultChannelId, - 'alice', - alice, - aliceSpaceDapp, - alicesWallet.address, - aliceProvider.wallet, - ) - - return { - alice, - bob, - alicesWallet, - bobsWallet, - carolsWallet, - aliceProvider, - bobProvider, - carolProvider, - aliceSpaceDapp, - bobSpaceDapp, - carolSpaceDapp, - spaceId, - defaultChannelId, - channelId, - roleId, - } -} - -async function expectUserCanJoinChannel(client: Client, channelId: string) { - await expect(client.joinStream(channelId!)).toResolve() - const aliceUserStreamView = (await client.waitForStream(makeUserStreamId(client.userId))!).view - // Wait for alice's user stream to have the join - await waitFor(() => aliceUserStreamView.userContent.isMember(channelId!, MembershipOp.SO_JOIN)) -} - describe('channelsWithEntitlements', () => { - test('userEntitlementPass', async () => { - const { alice, bob, channelId } = await setupChannelWithCustomRole(['alice'], NoopRuleData) - - // Validate alice can join the channel - await expectUserCanJoinChannel(alice, channelId!) - - const doneStart = Date.now() - // kill the clients - await bob.stopSync() - await alice.stopSync() - log('Done', Date.now() - doneStart) - }) - - test('userEntitlementFail', async () => { - const { alice, bob, channelId } = await setupChannelWithCustomRole(['carol'], NoopRuleData) - - await expect(alice.joinStream(channelId!)).rejects.toThrow(/7:PERMISSION_DENIED/) - - const doneStart = Date.now() - // kill the clients - await bob.stopSync() - await alice.stopSync() - log('Done', Date.now() - doneStart) - }) - - test('userEntitlementPass - join as root, linked wallet whitelisted', async () => { - const { alice, aliceSpaceDapp, aliceProvider, carolProvider, bob, channelId } = - await setupChannelWithCustomRole(['carol'], NoopRuleData) - - // Link carol's wallet to alice's as root - await linkWallets(aliceSpaceDapp, aliceProvider.wallet, carolProvider.wallet) - - // Validate alice can join the channel - await expectUserCanJoinChannel(alice, channelId!) - - const doneStart = Date.now() - // kill the clients - await bob.stopSync() - await alice.stopSync() - log('Done', Date.now() - doneStart) - }) - - test('userEntitlementPass - join as linked wallet, root wallet whitelisted', async () => { - const { alice, carolSpaceDapp, aliceProvider, carolProvider, bob, channelId } = - await setupChannelWithCustomRole(['carol'], NoopRuleData) - - // Link alice's wallet to Carol's wallet as root - await linkWallets(carolSpaceDapp, carolProvider.wallet, aliceProvider.wallet) - - // Validate alice can join the channel - await expectUserCanJoinChannel(alice, channelId!) - - const doneStart = Date.now() - // kill the clients - await bob.stopSync() - await alice.stopSync() - log('Done linked-wallet-whitelist', Date.now() - doneStart) - }) - - test('oneNftGateJoinPass - join as root, asset in linked wallet', async () => { - const testNft1Address = await getContractAddress('TestNFT1') - const { - alice, - bob, - aliceSpaceDapp, - aliceProvider, - carolsWallet, - carolProvider, - channelId, - } = await setupChannelWithCustomRole([], getNftRuleData(testNft1Address as `0x${string}`)) - - // Link carol's wallet to alice's as root - await linkWallets(aliceSpaceDapp, aliceProvider.wallet, carolProvider.wallet) - - log('Minting an NFT for carols wallet, which is linked to alices wallet') - await publicMint('TestNFT1', carolsWallet.address as `0x${string}`) - - // Validate alice can join the channel - await expectUserCanJoinChannel(alice, channelId!) - - const doneStart = Date.now() - // kill the clients - await bob.stopSync() - await alice.stopSync() - log('Done', Date.now() - doneStart) - }) - - test('oneNftGateJoinPass - join as linked wallet, asset in root wallet', async () => { - const testNft1Address = await getContractAddress('TestNFT1') - const { - alice, - bob, - carolSpaceDapp, - aliceProvider, - carolsWallet, - carolProvider, - channelId, - } = await setupChannelWithCustomRole([], getNftRuleData(testNft1Address as `0x${string}`)) - - log("Joining alice's wallet as a linked wallet to carols root wallet") - await linkWallets(carolSpaceDapp, carolProvider.wallet, aliceProvider.wallet) - - log('Minting an NFT for carols wallet, which is the root to alices wallet') - await publicMint('TestNFT1', carolsWallet.address as `0x${string}`) - - log('expect that alice can join the space') - // Validate alice can join the channel - await expectUserCanJoinChannel(alice, channelId!) - - const doneStart = Date.now() - // kill the clients - await bob.stopSync() - await alice.stopSync() - log('Done', Date.now() - doneStart) - }) - - test('oneNftGateJoinPass', async () => { - const testNftAddress = await getContractAddress('TestNFT') - const { alice, alicesWallet, bob, channelId } = await setupChannelWithCustomRole( - [], - getNftRuleData(testNftAddress), - ) - - // Mint an nft for alice - she should be able to join now - log('Minting NFT for Alice', testNftAddress, alicesWallet.address) - await publicMint('TestNFT', alicesWallet.address as `0x${string}`) - - // Alice should be able to join the nft-gated channel since she has the required NFT token. - await expectUserCanJoinChannel(alice, channelId!) - - await bob.stopSync() - await alice.stopSync() - }) - - test('oneNftGateJoinFail', async () => { - const testNft1Address = await getContractAddress('TestNFT1') - const { alice, bob, channelId } = await setupChannelWithCustomRole( - [], - getNftRuleData(testNft1Address as `0x${string}`), - ) - - log('Alice about to attempt to join channel', { alicesUserId: alice.userId }) - await expect(alice.joinStream(channelId!)).rejects.toThrow(/7:PERMISSION_DENIED/) - - // kill the clients - await bob.stopSync() - await alice.stopSync() - }) - - test('twoNftGateJoinPass', async () => { - const testNft1Address = await getContractAddress('TestNFT1') - const testNft2Address = await getContractAddress('TestNFT2') - const { alice, bob, alicesWallet, channelId } = await setupChannelWithCustomRole( - [], - twoNftRuleData(testNft1Address, testNft2Address), + // Banning with entitlements — users need permission to ban other users. + test('adminsCanRedactChannelMessages', async () => { + log('start adminsCanRedactChannelMessages') + // set up the web3 provider and spacedap + const baseConfig = makeBaseChainConfig() + const bobsWallet = ethers.Wallet.createRandom() + const bobsContext = await makeUserContextFromWallet(bobsWallet) + const bobProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, bobsWallet) + await bobProvider.fundWallet() + const spaceDapp = createSpaceDapp(bobProvider, baseConfig.chainConfig) + + // create a user stream + const bob = await makeTestClient({ context: bobsContext }) + const bobsUserStreamId = makeUserStreamId(bob.userId) + + const pricingModules = await spaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { + everyone: true, + users: [], + ruleData: NoopRuleData, + }, + } + log('transaction start bob creating space') + const transaction = await spaceDapp.createSpace( + { + spaceName: 'bobs-space-metadata', + spaceMetadata: 'bobs-space-metadata', + channelName: 'general', // default channel name + membership: membershipInfo, + }, + bobProvider.wallet, ) + const receipt = await transaction.wait() + log('transaction receipt', receipt) + expect(receipt.status).toEqual(1) + const spaceAddress = spaceDapp.getSpaceAddress(receipt) + expect(spaceAddress).toBeDefined() + const spaceId = makeSpaceStreamId(spaceAddress!) + const channelId = makeDefaultChannelStreamId(spaceAddress!) + // then on the river node + await expect(bob.initializeUser({ spaceId })).toResolve() + bob.startSync() + const returnVal = await bob.createSpace(spaceId) + expect(returnVal.streamId).toEqual(spaceId) + // Now there must be "joined space" event in the user stream. + const bobUserStreamView = bob.stream(bobsUserStreamId)!.view + expect(bobUserStreamView).toBeDefined() + expect(bobUserStreamView.userContent.isMember(spaceId, MembershipOp.SO_JOIN)).toBe(true) + + const waitForStreamPromise = makeDonePromise() + bob.on('userJoinedStream', (streamId) => { + if (streamId === channelId) { + waitForStreamPromise.done() + } + }) - const aliceMintTx1 = publicMint('TestNFT1', alicesWallet.address as `0x${string}`) - const aliceMintTx2 = publicMint('TestNFT2', alicesWallet.address as `0x${string}`) - - log('Minting nfts for alice') - await Promise.all([aliceMintTx1, aliceMintTx2]) - - log('expect that alice can join the channel') - // Validate alice can join the channel - await expectUserCanJoinChannel(alice, channelId!) - - // kill the clients - const doneStart = Date.now() - await bob.stopSync() - await alice.stopSync() - log('Done', Date.now() - doneStart) - }) - - test('twoNftGateJoinPass - acrossLinkedWallets', async () => { - const testNft1Address = await getContractAddress('TestNFT1') - const testNft2Address = await getContractAddress('TestNFT2') - const { - alice, - bob, - alicesWallet, - carolsWallet, - aliceSpaceDapp, - aliceProvider, - carolProvider, + // create the channel + log('Bob created space, about to create channel') + const channelProperties = 'Bobs channel properties' + const channelReturnVal = await bob.createChannel( + spaceId, + 'general', + channelProperties, channelId, - } = await setupChannelWithCustomRole([], twoNftRuleData(testNft1Address, testNft2Address)) - - const aliceMintTx1 = publicMint('TestNFT1', alicesWallet.address as `0x${string}`) - const carolMintTx2 = publicMint('TestNFT2', carolsWallet.address as `0x${string}`) - - log('Minting nfts for alice and carol') - await Promise.all([aliceMintTx1, carolMintTx2]) - - log("linking carols wallet to alice's wallet") - await linkWallets(aliceSpaceDapp, aliceProvider.wallet, carolProvider.wallet) - - log('Alice should be able to join channel with one asset in carol wallet') - await expectUserCanJoinChannel(alice, channelId!) - - // kill the clients - const doneStart = Date.now() - await bob.stopSync() - await alice.stopSync() - log('Done', Date.now() - doneStart) - }) - - test('twoNftGateJoinFail', async () => { - const testNft1Address = await getContractAddress('TestNFT1') - const testNft2Address = await getContractAddress('TestNFT2') - const { alice, bob, alicesWallet, channelId } = await setupChannelWithCustomRole( - [], - twoNftRuleData(testNft1Address, testNft2Address), ) + expect(channelReturnVal.streamId).toEqual(channelId) - // Mint only one of the required NFTs for alice - log('Minting only one of two required NFTs for alice') - await publicMint('TestNFT1', alicesWallet.address as `0x${string}`) - - log('expect that alice cannot join the channel') - await expect(alice.joinStream(channelId!)).rejects.toThrow(/7:PERMISSION_DENIED/) - - // kill the clients - await bob.stopSync() - await alice.stopSync() - }) + await waitFor(() => { + expect(bobUserStreamView.userContent.isMember(spaceId, MembershipOp.SO_JOIN)).toBe(true) + expect(bobUserStreamView.userContent.isMember(channelId, MembershipOp.SO_JOIN)).toBe( + true, + ) + }) - test('OrOfTwoNftGateJoinPass', async () => { - const testNft1Address = await getContractAddress('TestNFT1') - const testNft2Address = await getContractAddress('TestNFT2') - const { alice, bob, alicesWallet, channelId } = await setupChannelWithCustomRole( - [], - twoNftRuleData(testNft1Address, testNft2Address, LogicalOperationType.OR), - ) // join alice - log('Minting an NFT for alice') - await publicMint('TestNFT1', alicesWallet.address as `0x${string}`) - - log('expect that alice can join the channel') - await expectUserCanJoinChannel(alice, channelId!) - - // kill the clients - const doneStart = Date.now() - await bob.stopSync() - await alice.stopSync() - log('Done', Date.now() - doneStart) - }) - - test('orOfTwoNftOrOneNftGateJoinPass', async () => { - const testNft1Address = await getContractAddress('TestNFT1') - const testNft2Address = await getContractAddress('TestNFT2') - const testNft3Address = await getContractAddress('TestNFT3') - const leftOperation: Operation = { - opType: OperationType.CHECK, - checkType: CheckOperationType.ERC721, - chainId: 31337n, - contractAddress: testNft1Address as `0x${string}`, - threshold: 1n, - } - - const rightOperation: Operation = { - opType: OperationType.CHECK, - checkType: CheckOperationType.ERC721, - chainId: 31337n, - contractAddress: testNft2Address as `0x${string}`, - threshold: 1n, - } - const two: Operation = { - opType: OperationType.LOGICAL, - logicalType: LogicalOperationType.AND, - leftOperation, - rightOperation, - } - - const root: Operation = { - opType: OperationType.LOGICAL, - logicalType: LogicalOperationType.OR, - leftOperation: two, - rightOperation: { - opType: OperationType.CHECK, - checkType: CheckOperationType.ERC721, - chainId: 31337n, - contractAddress: testNft3Address as `0x${string}`, - threshold: 1n, - }, - } - - const ruleData = treeToRuleData(root) - const { alice, bob, alicesWallet, channelId } = await setupChannelWithCustomRole( - [], - ruleData, - ) - - log("Mint Alice's NFTs") - const aliceMintTx1 = publicMint('TestNFT1', alicesWallet.address as `0x${string}`) - const aliceMintTx2 = publicMint('TestNFT2', alicesWallet.address as `0x${string}`) - await Promise.all([aliceMintTx1, aliceMintTx2]) - - log('expect that alice can join the channel') - await expectUserCanJoinChannel(alice, channelId!) - - // kill the clients - const doneStart = Date.now() - await bob.stopSync() - await alice.stopSync() - log('Done', Date.now() - doneStart) - }) - - // Banning with entitlements — users need permission to ban other users. - test('adminsCanRedactChannelMessages', async () => { - // log('start adminsCanRedactChannelMessages') - // // set up the web3 provider and spacedapp - const { - alice, - bob, - alicesWallet, - aliceProvider, - bobProvider, - aliceSpaceDapp, - bobSpaceDapp, - } = await setupWalletsAndContexts() - - const { spaceId, defaultChannelId } = await createSpaceAndDefaultChannel( - bob, - bobSpaceDapp, - bobProvider.wallet, - 'bob', - await everyoneMembershipStruct(bobSpaceDapp, bob), - ) - bob.startSync() + const alicesWallet = ethers.Wallet.createRandom() + const alicesContext = await makeUserContextFromWallet(alicesWallet) + const aliceProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, alicesWallet) + await aliceProvider.fundWallet() + const alice = await makeTestClient({ + context: alicesContext, + }) + log('Alice created user, about to join space', { alicesUserId: alice.userId }) - // // Alice should have no issue joining the space and default channel. - await expectUserCanJoin( + // first join the space on chain + const aliceSpaceDapp = createSpaceDapp(aliceProvider, baseConfig.chainConfig) + log('transaction start alice joining space') + const { issued } = await aliceSpaceDapp.joinSpace( spaceId, - defaultChannelId, - 'alice', - alice, - aliceSpaceDapp, alicesWallet.address, aliceProvider.wallet, ) + expect(issued).toBe(true) + + await alice.initializeUser({ spaceId }) + alice.startSync() + await expect(alice.joinStream(spaceId)).toResolve() + await expect(alice.joinStream(channelId)).toResolve() + + const aliceUserStreamView = alice.stream(alice.userStreamId!)!.view + await waitFor(() => { + expect(aliceUserStreamView.userContent.isMember(spaceId, MembershipOp.SO_JOIN)).toBe( + true, + ) + expect(aliceUserStreamView.userContent.isMember(channelId, MembershipOp.SO_JOIN)).toBe( + true, + ) + }) // Alice says something bad - const stream = await alice.waitForStream(defaultChannelId) - await alice.sendMessage(defaultChannelId, 'Very bad message!') + const stream = await alice.waitForStream(channelId) + await alice.sendMessage(channelId, 'Very bad message!') let eventId: string | undefined await waitFor(() => { const event = stream.view.timeline.find( @@ -502,8 +172,8 @@ describe('channelsWithEntitlements', () => { expect(stream).toBeDefined() expect(eventId).toBeDefined() - await expect(bob.redactMessage(defaultChannelId, eventId!)).toResolve() - await expect(alice.redactMessage(defaultChannelId, eventId!)).rejects.toThrow( + await expect(bob.redactMessage(channelId, eventId!)).toResolve() + await expect(alice.redactMessage(channelId, eventId!)).rejects.toThrow( expect.objectContaining({ message: expect.stringContaining('7:PERMISSION_DENIED'), }), diff --git a/core/sdk/src/spaceWithEntitlements.test.ts b/core/sdk/src/spaceWithEntitlements.test.ts index 1c1aa6a397..f2a65bad88 100644 --- a/core/sdk/src/spaceWithEntitlements.test.ts +++ b/core/sdk/src/spaceWithEntitlements.test.ts @@ -5,111 +5,89 @@ import { getDynamicPricingModule, - everyoneMembershipStruct, + makeTestClient, + makeUserContextFromWallet, waitFor, createUserStreamAndSyncClient, createSpaceAndDefaultChannel, expectUserCanJoin, - setupWalletsAndContexts, linkWallets, - getNftRuleData, - twoNftRuleData, } from './util.test' +import { Client } from './client' import { dlog } from '@river-build/dlog' import { MembershipOp } from '@river-build/proto' +import { ethers } from 'ethers' import { CheckOperationType, ETH_ADDRESS, + LocalhostWeb3Provider, LogicalOperationType, MembershipStruct, NoopRuleData, Operation, OperationType, Permission, + createSpaceDapp, getContractAddress, publicMint, treeToRuleData, - IRuleEntitlement, + ISpaceDapp, } from '@river-build/web3' +import { makeBaseChainConfig } from './riverConfig' const log = dlog('csb:test:spaceWithEntitlements') -// Users need to be mapped from 'alice', 'bob', etc to their wallet addresses, -// because the wallets are created within this helper method. -async function createTownWithRequirements(requirements: { - everyone: boolean - users: string[] - ruleData: IRuleEntitlement.RuleDataStruct -}) { - const { - alice, - bob, - aliceSpaceDapp, - bobSpaceDapp, - carolSpaceDapp, - aliceProvider, - bobProvider, - carolProvider, - alicesWallet, - bobsWallet, - carolsWallet, - } = await setupWalletsAndContexts() - - const pricingModules = await bobSpaceDapp.listPricingModules() - const dynamicPricingModule = getDynamicPricingModule(pricingModules) - expect(dynamicPricingModule).toBeDefined() - - const userNameToWallet: Record = { - alice: alicesWallet.address, - bob: bobsWallet.address, - carol: carolsWallet.address, - } - requirements.users = requirements.users.map((user) => userNameToWallet[user]) - - const membershipInfo: MembershipStruct = { - settings: { - name: 'Everyone', - symbol: 'MEMBER', - price: 0, - maxSupply: 1000, - duration: 0, - currency: ETH_ADDRESS, - feeRecipient: bob.userId, - freeAllocation: 0, - pricingModule: dynamicPricingModule!.module, - }, - permissions: [Permission.Read, Permission.Write], - requirements, - } - - // This helper method validates that the owner can join the space and default channel. - const { - spaceId, - defaultChannelId: channelId, - userStreamView: bobUserStreamView, - } = await createSpaceAndDefaultChannel( - bob, - bobSpaceDapp, - bobProvider.wallet, - 'bobs', - membershipInfo, - ) +async function setupWalletsAndContexts() { + const baseConfig = makeBaseChainConfig() + + const [alicesWallet, bobsWallet, carolsWallet] = await Promise.all([ + ethers.Wallet.createRandom(), + ethers.Wallet.createRandom(), + ethers.Wallet.createRandom(), + ]) + + const [alicesContext, bobsContext] = await Promise.all([ + makeUserContextFromWallet(alicesWallet), + makeUserContextFromWallet(bobsWallet), + ]) + + const aliceProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, alicesWallet) + const bobProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, bobsWallet) + const carolProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, carolsWallet) + + await Promise.all([ + aliceProvider.fundWallet(), + bobProvider.fundWallet(), + carolProvider.fundWallet(), + ]) + + const bobSpaceDapp = createSpaceDapp(bobProvider, baseConfig.chainConfig) + const aliceSpaceDapp = createSpaceDapp(aliceProvider, baseConfig.chainConfig) + const carolSpaceDapp = createSpaceDapp(carolProvider, baseConfig.chainConfig) + + // create a user + const [alice, bob] = await Promise.all([ + makeTestClient({ + context: alicesContext, + }), + makeTestClient({ context: bobsContext }), + ]) return { alice, bob, - aliceSpaceDapp, - bobSpaceDapp, - carolSpaceDapp, - aliceProvider, - bobProvider, - carolProvider, alicesWallet, bobsWallet, + alicesContext, + bobsContext, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + // Return a third wallet / provider for wallet linking carolsWallet, - spaceId, - channelId, - bobUserStreamView, + carolProvider, + carolSpaceDapp, } } @@ -126,21 +104,68 @@ describe('spaceWithEntitlements', () => { // Banning with entitlements — users need permission to ban other users. test('ownerCanBanOtherUsers', async () => { log('start ownerCanBanOtherUsers') + // set up the web3 provider and spacedap + const baseConfig = makeBaseChainConfig() + + const bobsWallet = ethers.Wallet.createRandom() + const bobsContext = await makeUserContextFromWallet(bobsWallet) + const bobProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, bobsWallet) + await bobProvider.fundWallet() + const bobSpaceDapp = createSpaceDapp(bobProvider, baseConfig.chainConfig) + + // create a user stream + const bob = await makeTestClient({ context: bobsContext }) + + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { + everyone: true, + users: [], + ruleData: NoopRuleData, + }, + } const { - alice, - bob, - aliceSpaceDapp, - aliceProvider, - alicesWallet, spaceId, - channelId, - bobUserStreamView, - } = await createTownWithRequirements({ - everyone: true, - users: [], - ruleData: NoopRuleData, + defaultChannelId: channelId, + userStreamView: bobUserStreamView, + } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) + + // join alice + const alicesWallet = ethers.Wallet.createRandom() + const alicesContext = await makeUserContextFromWallet(alicesWallet) + const alice = await makeTestClient({ + context: alicesContext, }) + const aliceProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, alicesWallet) + await aliceProvider.fundWallet() + + const aliceSpaceDapp = createSpaceDapp(aliceProvider, baseConfig.chainConfig) + // await expect(alice.joinStream(spaceId)).rejects.toThrow() // todo log('Alice should be able to join space') @@ -189,12 +214,56 @@ describe('spaceWithEntitlements', () => { }) test('userEntitlementPass', async () => { - const { alice, bob, aliceSpaceDapp, aliceProvider, alicesWallet, spaceId, channelId } = - await createTownWithRequirements({ + const createAliceAndBobStart = Date.now() + const { + alice, + bob, + alicesWallet, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + } = await setupWalletsAndContexts() + + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { everyone: false, - users: ['alice'], + users: [alicesWallet.address], ruleData: NoopRuleData, - }) + }, + } + + const { spaceId, defaultChannelId: channelId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) await expectUserCanJoin( spaceId, @@ -214,28 +283,64 @@ describe('spaceWithEntitlements', () => { }) test('userEntitlementFail', async () => { - const { alice, bob, aliceSpaceDapp, alicesWallet, aliceProvider, spaceId } = - await createTownWithRequirements({ + const createAliceAndBobStart = Date.now() + const { + alice, + bob, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + carolsWallet, + } = await setupWalletsAndContexts() + + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { everyone: false, - users: ['carol'], // not alice! + users: [carolsWallet.address], // Alice not whitelisted ruleData: NoopRuleData, - }) + }, + } - // Alice cannot join the space in the contract. - const { issued } = await aliceSpaceDapp.joinSpace( - spaceId, - alicesWallet.address, - aliceProvider.wallet, + const { spaceId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, ) - expect(issued).toBe(false) // Have alice create a user stream attached to her own space. - // Then she will attempt to join the space from the client, which should also fail. + // Then she will attempt to join the space from the client, which should fail. await createUserStreamAndSyncClient( alice, aliceSpaceDapp, 'alice', - await everyoneMembershipStruct(aliceSpaceDapp, alice), + membershipInfo, aliceProvider.wallet, ) @@ -249,37 +354,74 @@ describe('spaceWithEntitlements', () => { log('Done', Date.now() - doneStart) }) - // This test is commented out as the membership joinSpace does not check linked wallets - // against the user entitlement. - test.skip('userEntitlementPass - join as root, linked wallet whitelisted', async () => { + test('userEntitlementPass - join as root, linked wallet whitelisted', async () => { + const createAliceAndBobStart = Date.now() const { alice, bob, - aliceSpaceDapp, - alicesWallet, aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + carolsWallet, carolProvider, - spaceId, - channelId, - } = await createTownWithRequirements({ - everyone: false, - users: ['carol'], // not alice! - ruleData: NoopRuleData, - }) + } = await setupWalletsAndContexts() + + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { + everyone: false, + users: [carolsWallet.address], // Alice not whitelisted + ruleData: NoopRuleData, + }, + } + + const { spaceId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) + await linkWallets(aliceSpaceDapp, aliceProvider.wallet, carolProvider.wallet) - // Alice should be able to join the space on the stream node. - log('Alice should be able to join space', spaceId) - await expectUserCanJoin( - spaceId, - channelId, - 'alice', + // Have alice create a user stream attached to her own space. + // Then she will attempt to join the space from the client, which should fail. + await createUserStreamAndSyncClient( alice, aliceSpaceDapp, - alicesWallet.address, + 'alice', + membershipInfo, aliceProvider.wallet, ) + // Alice cannot join the space on the stream node. + await expect(alice.joinStream(spaceId)).rejects.toThrow(/PERMISSION_DENIED/) + // Kill the clients const doneStart = Date.now() await bob.stopSync() @@ -287,39 +429,75 @@ describe('spaceWithEntitlements', () => { log('Done', Date.now() - doneStart) }) - // This test is commented out as the membership joinSpace does not check linked wallets - // against the user entitlement. - test.skip('userEntitlementPass - join as linked wallet, root wallet whitelisted', async () => { + test('userEntitlementPass - join as linked wallet, root wallet whitelisted', async () => { + const createAliceAndBobStart = Date.now() const { alice, bob, - aliceSpaceDapp, - carolSpaceDapp, aliceProvider, - alicesWallet, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + carolsWallet, carolProvider, - spaceId, - channelId, - } = await createTownWithRequirements({ - everyone: false, - users: ['carol'], // not alice! - ruleData: NoopRuleData, - }) + carolSpaceDapp, + } = await setupWalletsAndContexts() + + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { + everyone: false, + users: [carolsWallet.address], // Alice not whitelisted + ruleData: NoopRuleData, + }, + } + + const { spaceId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) await linkWallets(carolSpaceDapp, carolProvider.wallet, aliceProvider.wallet) - // Alice should be able to join the space on the stream node. - log('Alice should be able to join space', spaceId) - await expectUserCanJoin( - spaceId, - channelId, - 'alice', + // Have alice create a user stream attached to her own space. + // Then she will attempt to join the space from the client, which should fail. + await createUserStreamAndSyncClient( alice, aliceSpaceDapp, - alicesWallet.address, + 'alice', + membershipInfo, aliceProvider.wallet, ) + // Alice cannot join the space on the stream node. + await expect(alice.joinStream(spaceId)).rejects.toThrow(/PERMISSION_DENIED/) + // Kill the clients const doneStart = Date.now() await bob.stopSync() @@ -328,21 +506,70 @@ describe('spaceWithEntitlements', () => { }) test('oneNftGateJoinPass - join as root, asset in linked wallet', async () => { + const createAliceAndBobStart = Date.now() + const { alice, bob, - aliceSpaceDapp, - aliceProvider, alicesWallet, - carolsWallet, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, carolProvider, - spaceId, - channelId, - } = await createTownWithRequirements({ - everyone: false, - users: [], - ruleData: getNftRuleData(testNft1Address as `0x${string}`), - }) + carolsWallet, + } = await setupWalletsAndContexts() + + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { + everyone: false, + users: [], + ruleData: { + operations: [{ opType: OperationType.CHECK, index: 0 }], + checkOperations: [ + { + opType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: testNft1Address, + threshold: 1n, + }, + ], + logicalOperations: [], + }, + }, + } + + const { spaceId, defaultChannelId: channelId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) await linkWallets(aliceSpaceDapp, aliceProvider.wallet, carolProvider.wallet) @@ -368,22 +595,71 @@ describe('spaceWithEntitlements', () => { }) test('oneNftGateJoinPass - join as linked wallet, asset in root wallet', async () => { + const createAliceAndBobStart = Date.now() + const { alice, bob, - aliceSpaceDapp, - aliceProvider, alicesWallet, - carolsWallet, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, carolProvider, + carolsWallet, carolSpaceDapp, - spaceId, - channelId, - } = await createTownWithRequirements({ - everyone: false, - users: [], - ruleData: getNftRuleData(testNft1Address as `0x${string}`), - }) + } = await setupWalletsAndContexts() + + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { + everyone: false, + users: [], + ruleData: { + operations: [{ opType: OperationType.CHECK, index: 0 }], + checkOperations: [ + { + opType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: testNft1Address, + threshold: 1n, + }, + ], + logicalOperations: [], + }, + }, + } + + const { spaceId, defaultChannelId: channelId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) log("Joining alice's wallet as a linked wallet to carols root wallet") await linkWallets(carolSpaceDapp, carolProvider.wallet, aliceProvider.wallet) @@ -411,12 +687,68 @@ describe('spaceWithEntitlements', () => { }) test('oneNftGateJoinPass', async () => { - const { alice, bob, aliceSpaceDapp, aliceProvider, alicesWallet, spaceId, channelId } = - await createTownWithRequirements({ + const createAliceAndBobStart = Date.now() + + const { + alice, + bob, + alicesWallet, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + } = await setupWalletsAndContexts() + + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { everyone: false, users: [], - ruleData: getNftRuleData(testNft1Address as `0x${string}`), - }) + ruleData: { + operations: [{ opType: OperationType.CHECK, index: 0 }], + checkOperations: [ + { + opType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: testNft1Address, + threshold: 1n, + }, + ], + logicalOperations: [], + }, + }, + } + + const { spaceId, defaultChannelId: channelId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) // join alice log('Minting an NFT for alice') @@ -440,20 +772,86 @@ describe('spaceWithEntitlements', () => { }) test('oneNftGateJoinFail', async () => { - const { alice, bob, aliceSpaceDapp, aliceProvider, alicesWallet, spaceId } = - await createTownWithRequirements({ + const createAliceAndBobStart = Date.now() + + const { + alice, + bob, + alicesWallet, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + } = await setupWalletsAndContexts() + + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + log('aliceWallet', alicesWallet.address) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { everyone: false, users: [], - ruleData: getNftRuleData(testNft1Address as `0x${string}`), - }) + ruleData: { + operations: [{ opType: OperationType.CHECK, index: 0 }], + checkOperations: [ + { + opType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: testNft1Address, + threshold: 1n, + }, + ], + logicalOperations: [], + }, + }, + } + log('transaction start bob creating space') + const { spaceId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) + + log('Alice about to join space', { alicesUserId: alice.userId }) + + // first join the space on chain + const aliceJoinStart = Date.now() + log('transaction start Alice joining space') - log('Alice about to attempt to join space', { alicesUserId: alice.userId }) const { issued } = await aliceSpaceDapp.joinSpace( spaceId, alicesWallet.address, aliceProvider.wallet, ) expect(issued).toBe(false) + log( + 'Alice correctly failed to join space and has a MembershipNFT', + Date.now() - aliceJoinStart, + ) // Have alice create a user stream attached to her own space. // Then she will attempt to join the space from the client, which should fail. @@ -461,7 +859,7 @@ describe('spaceWithEntitlements', () => { alice, aliceSpaceDapp, 'alice', - await everyoneMembershipStruct(aliceSpaceDapp, alice), + membershipInfo, aliceProvider.wallet, ) @@ -474,16 +872,85 @@ describe('spaceWithEntitlements', () => { }) test('twoNftGateJoinPass', async () => { - const { alice, bob, aliceSpaceDapp, aliceProvider, alicesWallet, spaceId, channelId } = - await createTownWithRequirements({ - everyone: false, - users: [], - ruleData: twoNftRuleData(testNft1Address, testNft2Address), - }) + const createAliceAndBobStart = Date.now() + + const { + alice, + bob, + alicesWallet, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + } = await setupWalletsAndContexts() const aliceMintTx1 = publicMint('TestNFT1', alicesWallet.address as `0x${string}`) const aliceMintTx2 = publicMint('TestNFT2', alicesWallet.address as `0x${string}`) + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + const leftOperation: Operation = { + opType: OperationType.CHECK, + checkType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: testNft1Address as `0x${string}`, + threshold: 1n, + } + + const rightOperation: Operation = { + opType: OperationType.CHECK, + checkType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: testNft2Address as `0x${string}`, + threshold: 1n, + } + const root: Operation = { + opType: OperationType.LOGICAL, + logicalType: LogicalOperationType.AND, + leftOperation, + rightOperation, + } + + const ruleData = treeToRuleData(root) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { + everyone: false, + users: [], + ruleData, + }, + } + log('transaction start bob creating space') + const { spaceId, defaultChannelId: channelId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) + log('Minting nfts for alice') await Promise.all([aliceMintTx1, aliceMintTx2]) @@ -506,28 +973,88 @@ describe('spaceWithEntitlements', () => { }) test('twoNftGateJoinPass - acrossLinkedWallets', async () => { + const createAliceAndBobStart = Date.now() + const { alice, bob, - aliceSpaceDapp, + alicesWallet, aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, carolProvider, - alicesWallet, carolsWallet, - spaceId, - channelId, - } = await createTownWithRequirements({ - everyone: false, - users: [], - ruleData: twoNftRuleData(testNft1Address, testNft2Address), - }) + } = await setupWalletsAndContexts() const aliceMintTx1 = publicMint('TestNFT1', alicesWallet.address as `0x${string}`) const carolMintTx2 = publicMint('TestNFT2', carolsWallet.address as `0x${string}`) + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + + const leftOperation: Operation = { + opType: OperationType.CHECK, + checkType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: testNft1Address as `0x${string}`, + threshold: 1n, + } + + const rightOperation: Operation = { + opType: OperationType.CHECK, + checkType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: testNft2Address as `0x${string}`, + threshold: 1n, + } + const root: Operation = { + opType: OperationType.LOGICAL, + logicalType: LogicalOperationType.AND, + leftOperation, + rightOperation, + } + + const ruleData = treeToRuleData(root) + + // create a space stream, + log('Bob created user, about to create space') + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { + everyone: false, + users: [], + ruleData, + }, + } + const { spaceId, defaultChannelId: channelId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) + log('Minting nfts for alice and carol') await Promise.all([aliceMintTx1, carolMintTx2]) - log("linking carols wallet to alice's wallet") await linkWallets(aliceSpaceDapp, aliceProvider.wallet, carolProvider.wallet) @@ -549,13 +1076,89 @@ describe('spaceWithEntitlements', () => { log('Done', Date.now() - doneStart) }) - test('twoNftGateJoinFail', async () => { - const { alice, bob, aliceSpaceDapp, aliceProvider, alicesWallet, spaceId } = - await createTownWithRequirements({ + async function twoNftMembershipInfo( + spaceDapp: ISpaceDapp, + client: Client, + nft1Address: string, + nft2Address: string, + logOpType: LogicalOperationType.AND | LogicalOperationType.OR = LogicalOperationType.AND, + ): Promise { + const pricingModules = await spaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + const leftOperation: Operation = { + opType: OperationType.CHECK, + checkType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: nft1Address as `0x${string}`, + threshold: 1n, + } + + const rightOperation: Operation = { + opType: OperationType.CHECK, + checkType: CheckOperationType.ERC721, + chainId: 31337n, + contractAddress: nft2Address as `0x${string}`, + threshold: 1n, + } + const root: Operation = { + opType: OperationType.LOGICAL, + logicalType: logOpType, + leftOperation, + rightOperation, + } + + const ruleData = treeToRuleData(root) + + return { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: client.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { everyone: false, users: [], - ruleData: twoNftRuleData(testNft1Address, testNft2Address), - }) + ruleData, + }, + } + } + + test('twoNftGateJoinFail', async () => { + const createAliceAndBobStart = Date.now() + const { + alice, + bob, + alicesWallet, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + } = await setupWalletsAndContexts() + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const membershipInfo = await twoNftMembershipInfo( + bobSpaceDapp, + bob, + testNft1Address, + testNft2Address, + ) + + const { spaceId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bob', + membershipInfo, + ) // join alice log('Minting an NFT for alice') @@ -578,11 +1181,11 @@ describe('spaceWithEntitlements', () => { alice, aliceSpaceDapp, 'alice', - await everyoneMembershipStruct(aliceSpaceDapp, alice), + membershipInfo, aliceProvider.wallet, ) // Alice cannot join the space on the stream node. - await expect(alice.joinStream(spaceId)).rejects.toThrow(/7:PERMISSION_DENIED/) + await expect(alice.joinStream(spaceId)).rejects.toThrow('PERMISSION_DENIED') // kill the clients await bob.stopSync() @@ -590,12 +1193,34 @@ describe('spaceWithEntitlements', () => { }) test('OrOfTwoNftGateJoinPass', async () => { - const { alice, bob, aliceSpaceDapp, aliceProvider, alicesWallet, spaceId, channelId } = - await createTownWithRequirements({ - everyone: false, - users: [], - ruleData: twoNftRuleData(testNft1Address, testNft2Address, LogicalOperationType.OR), - }) + const createAliceAndBobStart = Date.now() + + const { + alice, + bob, + alicesWallet, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + } = await setupWalletsAndContexts() + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const membershipInfo = await twoNftMembershipInfo( + bobSpaceDapp, + bob, + testNft1Address, + testNft2Address, + LogicalOperationType.OR, + ) + + const { spaceId, defaultChannelId: channelId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bobs', + membershipInfo, + ) // join alice log('Minting an NFT for alice') @@ -621,6 +1246,30 @@ describe('spaceWithEntitlements', () => { }) test('orOfTwoNftOrOneNftGateJoinPass', async () => { + const createAliceAndBobStart = Date.now() + + const { + alice, + bob, + alicesWallet, + aliceProvider, + bobProvider, + aliceSpaceDapp, + bobSpaceDapp, + } = await setupWalletsAndContexts() + + const aliceMintTx1 = publicMint('TestNFT1', alicesWallet.address as `0x${string}`) + const aliceMintTx2 = publicMint('TestNFT2', alicesWallet.address as `0x${string}`) + + log('createAliceAndBobStart took', Date.now() - createAliceAndBobStart) + + const listPricingModulesStart = Date.now() + const pricingModules = await bobSpaceDapp.listPricingModules() + const dynamicPricingModule = getDynamicPricingModule(pricingModules) + expect(dynamicPricingModule).toBeDefined() + + log('listPricingModules took', Date.now() - listPricingModulesStart) + const leftOperation: Operation = { opType: OperationType.CHECK, checkType: CheckOperationType.ERC721, @@ -657,16 +1306,38 @@ describe('spaceWithEntitlements', () => { } const ruleData = treeToRuleData(root) - const { alice, bob, aliceSpaceDapp, aliceProvider, alicesWallet, spaceId, channelId } = - await createTownWithRequirements({ + + // create a space stream, + log('Bob created user, about to create space', ruleData, ruleData) + // first on the blockchain + const membershipInfo: MembershipStruct = { + settings: { + name: 'Everyone', + symbol: 'MEMBER', + price: 0, + maxSupply: 1000, + duration: 0, + currency: ETH_ADDRESS, + feeRecipient: bob.userId, + freeAllocation: 0, + pricingModule: dynamicPricingModule!.module, + }, + permissions: [Permission.Read, Permission.Write], + requirements: { everyone: false, users: [], ruleData, - }) + }, + } + const { spaceId, defaultChannelId: channelId } = await createSpaceAndDefaultChannel( + bob, + bobSpaceDapp, + bobProvider.wallet, + 'bob', + membershipInfo, + ) log("Mint Alice's NFTs") - const aliceMintTx1 = publicMint('TestNFT1', alicesWallet.address as `0x${string}`) - const aliceMintTx2 = publicMint('TestNFT2', alicesWallet.address as `0x${string}`) await Promise.all([aliceMintTx1, aliceMintTx2]) log('expect alice can join space') diff --git a/core/sdk/src/util.test.ts b/core/sdk/src/util.test.ts index 7bcfe3d49e..81d9b3c193 100644 --- a/core/sdk/src/util.test.ts +++ b/core/sdk/src/util.test.ts @@ -13,12 +13,10 @@ import { import { PlainMessage } from '@bufbuild/protobuf' import { StreamStateView } from './streamStateView' import { Client } from './client' -import { makeBaseChainConfig, makeRiverChainConfig } from './riverConfig' import { genId, makeSpaceStreamId, makeDefaultChannelStreamId, - makeUniqueChannelStreamId, makeUserStreamId, userIdFromAddress, } from './id' @@ -34,24 +32,13 @@ import _ from 'lodash' import { MockEntitlementsDelegate } from './utils' import { SignerContext, makeSignerContext } from './signerContext' import { + IArchitectBase, + ISpaceDapp, LocalhostWeb3Provider, PricingModuleStruct, - createExternalNFTStruct, createRiverRegistry, - createSpaceDapp, - IRuleEntitlement, - Permission, - ISpaceDapp, - IArchitectBase, - ETH_ADDRESS, - MembershipStruct, - NoopRuleData, - CheckOperationType, - LogicalOperationType, - Operation, - OperationType, - treeToRuleData, } from '@river-build/web3' +import { makeRiverChainConfig } from './riverConfig' const log = dlog('csb:test:util') @@ -172,60 +159,6 @@ export const makeTestClient = async (opts?: TestClientOpts): Promise => return new Client(context, rpcClient, cryptoStore, entitlementsDelegate, persistenceDbName) } -export async function setupWalletsAndContexts() { - const baseConfig = makeBaseChainConfig() - - const [alicesWallet, bobsWallet, carolsWallet] = await Promise.all([ - ethers.Wallet.createRandom(), - ethers.Wallet.createRandom(), - ethers.Wallet.createRandom(), - ]) - - const [alicesContext, bobsContext] = await Promise.all([ - makeUserContextFromWallet(alicesWallet), - makeUserContextFromWallet(bobsWallet), - ]) - - const aliceProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, alicesWallet) - const bobProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, bobsWallet) - const carolProvider = new LocalhostWeb3Provider(baseConfig.rpcUrl, carolsWallet) - - await Promise.all([ - aliceProvider.fundWallet(), - bobProvider.fundWallet(), - carolProvider.fundWallet(), - ]) - - const bobSpaceDapp = createSpaceDapp(bobProvider, baseConfig.chainConfig) - const aliceSpaceDapp = createSpaceDapp(aliceProvider, baseConfig.chainConfig) - const carolSpaceDapp = createSpaceDapp(carolProvider, baseConfig.chainConfig) - - // create a user - const [alice, bob] = await Promise.all([ - makeTestClient({ - context: alicesContext, - }), - makeTestClient({ context: bobsContext }), - ]) - - return { - alice, - bob, - alicesWallet, - bobsWallet, - alicesContext, - bobsContext, - aliceProvider, - bobProvider, - aliceSpaceDapp, - bobSpaceDapp, - // Return a third wallet / provider for wallet linking - carolsWallet, - carolProvider, - carolSpaceDapp, - } -} - class DonePromise { promise: Promise // @ts-ignore: Promise body is executed immediately, so vars are assigned before constructor returns @@ -436,65 +369,6 @@ export async function expectUserCanJoin( }) } -export async function everyoneMembershipStruct( - spaceDapp: ISpaceDapp, - client: Client, -): Promise { - const pricingModules = await spaceDapp.listPricingModules() - const dynamicPricingModule = getDynamicPricingModule(pricingModules) - expect(dynamicPricingModule).toBeDefined() - - return { - settings: { - name: 'Everyone', - symbol: 'MEMBER', - price: 0, - maxSupply: 1000, - duration: 0, - currency: ETH_ADDRESS, - feeRecipient: client.userId, - freeAllocation: 0, - pricingModule: dynamicPricingModule!.module, - }, - permissions: [Permission.Read, Permission.Write], - requirements: { - everyone: true, - users: [], - ruleData: NoopRuleData, - }, - } -} - -export function twoNftRuleData( - nft1Address: string, - nft2Address: string, - logOpType: LogicalOperationType.AND | LogicalOperationType.OR = LogicalOperationType.AND, -): IRuleEntitlement.RuleDataStruct { - const leftOperation: Operation = { - opType: OperationType.CHECK, - checkType: CheckOperationType.ERC721, - chainId: 31337n, - contractAddress: nft1Address as `0x${string}`, - threshold: 1n, - } - - const rightOperation: Operation = { - opType: OperationType.CHECK, - checkType: CheckOperationType.ERC721, - chainId: 31337n, - contractAddress: nft2Address as `0x${string}`, - threshold: 1n, - } - const root: Operation = { - opType: OperationType.LOGICAL, - logicalType: logOpType, - leftOperation, - rightOperation, - } - - return treeToRuleData(root) -} - // Hint: pass in the wallets attached to the providers. export async function linkWallets( rootSpaceDapp: ISpaceDapp, @@ -513,9 +387,6 @@ export async function linkWallets( expect(txn).toBeDefined() const receipt = await txn?.wait() expect(receipt!.status).toEqual(1) - - const linkedWallets = await walletLink.getLinkedWallets(rootWallet.address) - expect(linkedWallets).toContain(linkedWallet.address) } export function waitFor( @@ -650,77 +521,3 @@ export const getDynamicPricingModule = (pricingModules: PricingModuleStruct[]) = export const getFixedPricingModule = (pricingModules: PricingModuleStruct[]) => { return pricingModules.find((module) => module.name === FIXED_PRICING) } - -export function getNftRuleData(testNftAddress: `0x${string}`): IRuleEntitlement.RuleDataStruct { - return createExternalNFTStruct([testNftAddress]) -} - -export interface CreateRoleContext { - roleId: number | undefined - error: Error | undefined -} - -export async function createRole( - spaceDapp: ISpaceDapp, - provider: ethers.providers.Provider, - spaceId: string, - roleName: string, - permissions: Permission[], - users: string[], - ruleData: IRuleEntitlement.RuleDataStruct, - signer: ethers.Signer, -): Promise { - let txn: ethers.ContractTransaction | undefined = undefined - let error: Error | undefined = undefined - - try { - txn = await spaceDapp.createRole(spaceId, roleName, permissions, users, ruleData, signer) - } catch (err) { - error = spaceDapp.parseSpaceError(spaceId, err) - return { roleId: undefined, error } - } - - const receipt = await provider.waitForTransaction(txn.hash) - if (receipt.status === 0) { - return { roleId: undefined, error: new Error('Transaction failed') } - } - - const parsedLogs = await spaceDapp.parseSpaceLogs(spaceId, receipt.logs) - const roleCreatedEvent = parsedLogs.find((log) => log?.name === 'RoleCreated') - if (!roleCreatedEvent) { - return { roleId: undefined, error: new Error('RoleCreated event not found') } - } - const roleId = (roleCreatedEvent.args[1] as ethers.BigNumber).toNumber() - return { roleId, error: undefined } -} - -export interface CreateChannelContext { - channelId: string | undefined - error: Error | undefined -} - -export async function createChannel( - spaceDapp: ISpaceDapp, - provider: ethers.providers.Provider, - spaceId: string, - channelName: string, - roleIds: number[], - signer: ethers.Signer, -): Promise { - let txn: ethers.ContractTransaction | undefined = undefined - let error: Error | undefined = undefined - - const channelId = makeUniqueChannelStreamId(spaceId) - try { - txn = await spaceDapp.createChannel(spaceId, channelName, channelId, roleIds, signer) - } catch (err) { - error = spaceDapp.parseSpaceError(spaceId, err) - return { channelId: undefined, error } - } - - const receipt = await provider.waitForTransaction(txn.hash) - if (receipt.status === 0) { - return { channelId: undefined, error: new Error('Transaction failed') } - } - return { channelId, error: undefined } -} diff --git a/core/web3/src/ConvertersRoles.ts b/core/web3/src/ConvertersRoles.ts index ef82fb3332..8b204b1af2 100644 --- a/core/web3/src/ConvertersRoles.ts +++ b/core/web3/src/ConvertersRoles.ts @@ -42,7 +42,7 @@ export async function createEntitlementStruct( entitlements.push(userEntitlement) } - if (ruleData.operations.length > 0) { + if (ruleData) { const ruleEntitlement: EntitlementStruct = createRuleEntitlementStruct( ruleEntitlementAddress as `0x{string}`, ruleData, diff --git a/core/web3/src/TestGatingNFT.ts b/core/web3/src/TestGatingNFT.ts index e96953ee97..6af447e4d6 100644 --- a/core/web3/src/TestGatingNFT.ts +++ b/core/web3/src/TestGatingNFT.ts @@ -188,6 +188,5 @@ export async function publicMint(nftName: string, toAddress: `0x${string}`) { account, }) - const receipt = await client.waitForTransactionReceipt({ hash: nftReceipt }) - expect(receipt.status).toBe('success') + await client.waitForTransactionReceipt({ hash: nftReceipt }) } diff --git a/core/web3/src/entitlement.ts b/core/web3/src/entitlement.ts index 072111871e..9d55764d8d 100644 --- a/core/web3/src/entitlement.ts +++ b/core/web3/src/entitlement.ts @@ -91,7 +91,7 @@ export const NoopOperation: NoOperation = { } export const NoopRuleData = { - operations: [], + operations: [NoopOperation], checkOperations: [], logicalOperations: [], } diff --git a/core/xchain/entitlement/check_operation.go b/core/xchain/entitlement/check_operation.go index 9e4702d67a..8331cf025f 100644 --- a/core/xchain/entitlement/check_operation.go +++ b/core/xchain/entitlement/check_operation.go @@ -26,23 +26,6 @@ func (e *Evaluator) evaluateCheckOperation( switch op.CheckType { case MOCK: return e.evaluateMockOperation(ctx, op) - case CheckNONE: - return false, fmt.Errorf("unknown operation") - } - - // Sanity checks - log := dlog.FromCtx(ctx).With("function", "evaluateCheckOperation") - if op.ChainID == nil { - log.Info("Chain ID is nil") - return false, fmt.Errorf("evaluateCheckOperation: Chain ID is nil") - } - zeroAddress := common.Address{} - if op.ContractAddress == zeroAddress { - log.Info("Contract address is nil") - return false, fmt.Errorf("evaluateCheckOperation: Contract address is nil") - } - - switch op.CheckType { case ISENTITLED: return e.evaluateIsEntitledOperation(ctx, op, linkedWallets) case ERC20: @@ -51,6 +34,8 @@ func (e *Evaluator) evaluateCheckOperation( return e.evaluateErc721Operation(ctx, op, linkedWallets) case ERC1155: return e.evaluateErc1155Operation(ctx, op) + case CheckNONE: + fallthrough default: return false, fmt.Errorf("unknown operation") } @@ -81,11 +66,11 @@ func (e *Evaluator) evaluateIsEntitledOperation( op *CheckOperation, linkedWallets []common.Address, ) (bool, error) { - log := dlog.FromCtx(ctx).With("function", "evaluateIsEntitledOperation") + log := dlog.FromCtx(ctx).With("function", "evaluateErc20Operation") client, err := e.clients.Get(op.ChainID.Uint64()) if err != nil { log.Error("Chain ID not found", "chainID", op.ChainID) - return false, fmt.Errorf("evaluateIsEntitledOperation: Chain ID %v not found", op.ChainID) + return false, fmt.Errorf("evaluateErc20Operation: Chain ID %v not found", op.ChainID) } customEntitlementChecker, err := contracts.NewICustomEntitlement( @@ -190,7 +175,7 @@ func (e *Evaluator) evaluateErc721Operation( client, err := e.clients.Get(op.ChainID.Uint64()) if err != nil { log.Error("Chain ID not found", "chainID", op.ChainID) - return false, fmt.Errorf("evaluateErc721Operation: Chain ID %v not found", op.ChainID) + return false, fmt.Errorf("evaluateErc20Operation: Chain ID %v not found", op.ChainID) } nft, err := erc721.NewErc721Caller(op.ContractAddress, client) diff --git a/core/xchain/entitlement/entitlement.go b/core/xchain/entitlement/entitlement.go index 442ce3e231..2b38a0cc5a 100644 --- a/core/xchain/entitlement/entitlement.go +++ b/core/xchain/entitlement/entitlement.go @@ -169,8 +169,7 @@ func (a *AndOperation) SetRightOperation(right Operation) { } func getOperationTree(ctx context.Context, - ruleData *er.IRuleData, -) (Operation, error) { + ruleData *er.IRuleData) (Operation, error) { log := dlog.FromCtx(ctx) decodedOperations := []Operation{} log.Debug("Decoding operations", "ruleData", ruleData) diff --git a/core/xchain/entitlement/linkedwallets.go b/core/xchain/entitlement/linkedwallets.go deleted file mode 100644 index bd58567c10..0000000000 --- a/core/xchain/entitlement/linkedwallets.go +++ /dev/null @@ -1,111 +0,0 @@ -package entitlement - -import ( - "context" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/prometheus/client_golang/prometheus" - "github.com/river-build/river/core/node/dlog" - "github.com/river-build/river/core/node/infra" - "github.com/river-build/river/core/xchain/contracts" -) - -type WrappedWalletLink interface { - GetRootKeyForWallet(ctx context.Context, wallet common.Address) (common.Address, error) - GetWalletsByRootKey(ctx context.Context, rootKey common.Address) ([]common.Address, error) -} - -type wrappedWalletLink struct { - contract *contracts.IWalletLink -} - -func (w *wrappedWalletLink) GetRootKeyForWallet(ctx context.Context, wallet common.Address) (common.Address, error) { - return w.contract.GetRootKeyForWallet(&bind.CallOpts{Context: ctx}, wallet) -} - -func (w *wrappedWalletLink) GetWalletsByRootKey(ctx context.Context, rootKey common.Address) ([]common.Address, error) { - return w.contract.GetWalletsByRootKey(&bind.CallOpts{Context: ctx}, rootKey) -} - -func NewWrappedWalletLink(contract *contracts.IWalletLink) WrappedWalletLink { - return &wrappedWalletLink{ - contract: contract, - } -} - -func GetLinkedWallets( - ctx context.Context, - wallet common.Address, - walletLink WrappedWalletLink, - callDurations *prometheus.HistogramVec, - getRootKeyForWalletCalls *infra.StatusCounterVec, - getWalletsByRootKeyCalls *infra.StatusCounterVec, -) ([]common.Address, error) { - log := dlog.FromCtx(ctx) - var timer *prometheus.Timer - - if callDurations != nil { - timer = prometheus.NewTimer(callDurations.WithLabelValues("GetRootKeyForWallet")) - } - rootKey, err := walletLink.GetRootKeyForWallet(ctx, wallet) - if timer != nil { - timer.ObserveDuration() - } - - if err != nil { - log.Error("Failed to GetRootKeyForWallet", "err", err, "wallet", wallet.Hex()) - if getRootKeyForWalletCalls != nil { - getRootKeyForWalletCalls.IncFail() - } - return nil, err - } - if getRootKeyForWalletCalls != nil { - getRootKeyForWalletCalls.IncPass() - } - - var zero common.Address - if rootKey == zero { - log.Debug("Wallet not linked to any root key, trying as root key", "wallet", wallet.Hex()) - rootKey = wallet - } - - if callDurations != nil { - timer = prometheus.NewTimer(callDurations.WithLabelValues("GetWalletsByRootKey")) - } - wallets, err := walletLink.GetWalletsByRootKey(ctx, rootKey) - if timer != nil { - timer.ObserveDuration() - } - if err != nil { - if getWalletsByRootKeyCalls != nil { - getWalletsByRootKeyCalls.IncFail() - } - return nil, err - } - if getWalletsByRootKeyCalls != nil { - getWalletsByRootKeyCalls.IncPass() - } - - if len(wallets) == 0 { - log.Debug("No linked wallets found", "rootKey", rootKey.Hex()) - return []common.Address{wallet}, nil - } - - // Make sure the root wallet is included in the returned list of linked wallets. This will not - // be the case when the wallet passed to the check is the root wallet. - containsRootWallet := false - for _, w := range wallets { - if w == rootKey { - containsRootWallet = true - break - } - } - if !containsRootWallet { - wallets = append(wallets, rootKey) - } - - log.Debug("Linked wallets", "rootKey", rootKey.Hex(), "wallets", wallets) - - return wallets, nil -} diff --git a/core/xchain/server/server.go b/core/xchain/server/server.go index ca6264443a..a5f5d9d822 100644 --- a/core/xchain/server/server.go +++ b/core/xchain/server/server.go @@ -8,7 +8,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/river-build/river/core/node/config" - . "github.com/river-build/river/core/node/protocol" "github.com/river-build/river/core/xchain/contracts" "github.com/river-build/river/core/xchain/entitlement" "github.com/river-build/river/core/xchain/util" @@ -23,6 +22,7 @@ import ( "github.com/river-build/river/core/node/crypto" "github.com/river-build/river/core/node/dlog" "github.com/river-build/river/core/node/infra" + . "github.com/river-build/river/core/node/protocol" ) type ( @@ -170,12 +170,7 @@ func New( } entCounter := metrics.NewStatusCounterVecEx("entitlement_checks", "Counters for entitelement check ops", "op") - contractCounter := metrics.NewStatusCounterVecEx( - "contract_calls", - "Contract calls fro entitlement checks", - "op", - "name", - ) + contractCounter := metrics.NewStatusCounterVecEx("contract_calls", "Contract calls fro entitlement checks", "op", "name") x := &xchain{ workerID: workerID, checker: checker, @@ -189,24 +184,11 @@ func New( metrics: metrics, entitlementCheckRequested: entCounter.MustCurryWith(map[string]string{"op": "requested"}), entitlementCheckProcessed: entCounter.MustCurryWith(map[string]string{"op": "processed"}), - entitlementCheckTx: contractCounter.MustCurryWith( - map[string]string{"op": "write", "name": "entitlement_check_tx"}, - ), - getRootKeyForWalletCalls: contractCounter.MustCurryWith( - map[string]string{"op": "read", "name": "get_root_key_for_wallet"}, - ), - getWalletsByRootKeyCalls: contractCounter.MustCurryWith( - map[string]string{"op": "read", "name": "get_wallets_by_root_key"}, - ), - getRuleDataCalls: contractCounter.MustCurryWith( - map[string]string{"op": "read", "name": "get_rule_data"}, - ), - callDurations: metrics.NewHistogramVecEx( - "call_duration_seconds", - "Durations of contract calls", - infra.DefaultDurationBucketsSeconds, - "op", - ), + entitlementCheckTx: contractCounter.MustCurryWith(map[string]string{"op": "write", "name": "entitlement_check_tx"}), + getRootKeyForWalletCalls: contractCounter.MustCurryWith(map[string]string{"op": "read", "name": "get_root_key_for_wallet"}), + getWalletsByRootKeyCalls: contractCounter.MustCurryWith(map[string]string{"op": "read", "name": "get_wallets_by_root_key"}), + getRuleDataCalls: contractCounter.MustCurryWith(map[string]string{"op": "read", "name": "get_rule_data"}), + callDurations: metrics.NewHistogramVecEx("call_duration_seconds", "Durations of contract calls", infra.DefaultDurationBucketsSeconds, "op"), } isRegistered, err := x.isRegistered(ctx) @@ -393,11 +375,7 @@ func (x *xchain) writeEntitlementCheckResults(ctx context.Context, checkResults } gasEstimate, err := x.baseChain.TxPool.EstimateGas(ctx, createPostResultTx) if err != nil { - log.Warn( - "Failed to estimate gas for PostEntitlementCheckResult (entitlement check complete?)", - "err", - err, - ) + log.Warn("Failed to estimate gas for PostEntitlementCheckResult (entitlement check complete?)", "err", err) } pendingTx, err := x.baseChain.TxPool.Submit( @@ -491,27 +469,51 @@ func (x *xchain) getLinkedWallets(ctx context.Context, wallet common.Address) ([ return nil, x.handleContractError(log, err, "Failed to create IWalletLink") } - wrapped := entitlement.NewWrappedWalletLink(iWalletLink) - wallets, err := entitlement.GetLinkedWallets( - ctx, - wallet, - wrapped, - x.callDurations, - x.getRootKeyForWalletCalls, - x.getWalletsByRootKeyCalls, - ) + timer := prometheus.NewTimer(x.callDurations.WithLabelValues("GetRootKeyForWallet")) + rootKey, err := iWalletLink.GetRootKeyForWallet(&bind.CallOpts{Context: ctx}, wallet) + timer.ObserveDuration() if err != nil { - log.Error( - "Failed to get linked wallets", - "err", - err, - "wallet", - wallet.Hex(), - "walletLinkContract", - x.config.GetWalletLinkContractAddress(), - ) - return nil, x.handleContractError(log, err, "Failed to get linked wallets") + log.Error("Failed to GetRootKeyForWallet", "err", err, "wallet", wallet.Hex(), "walletLinkContract", x.config.GetWalletLinkContractAddress()) + x.getRootKeyForWalletCalls.IncFail() + return nil, x.handleContractError(log, err, "Failed to GetRootKeyForWallet") + } + x.getRootKeyForWalletCalls.IncPass() + + var zero common.Address + if rootKey == zero { + log.Debug("Wallet not linked to any root key, trying as root key", "wallet", wallet.Hex()) + rootKey = wallet } + + timer = prometheus.NewTimer(x.callDurations.WithLabelValues("GetWalletsByRootKey")) + wallets, err := iWalletLink.GetWalletsByRootKey(&bind.CallOpts{Context: ctx}, rootKey) + timer.ObserveDuration() + if err != nil { + x.getWalletsByRootKeyCalls.IncFail() + return nil, x.handleContractError(log, err, "Failed to GetWalletsByRootKey") + } + x.getWalletsByRootKeyCalls.IncPass() + + if len(wallets) == 0 { + log.Debug("No linked wallets found", "rootKey", rootKey.Hex()) + return []common.Address{wallet}, nil + } + + // Make sure the root wallet is included in the returned list of linked wallets. This will not + // be the case when the wallet passed to the check is the root wallet. + containsRootWallet := false + for _, w := range wallets { + if w == rootKey { + containsRootWallet = true + break + } + } + if !containsRootWallet { + wallets = append(wallets, rootKey) + } + + log.Debug("Linked wallets", "rootKey", rootKey.Hex(), "wallets", wallets) + return wallets, nil } diff --git a/scripts/gen-river-node-bindings.sh b/scripts/gen-river-node-bindings.sh index d085cdfa60..482709c545 100755 --- a/scripts/gen-river-node-bindings.sh +++ b/scripts/gen-river-node-bindings.sh @@ -57,9 +57,7 @@ generate_go base IArchitect architect generate_go base Channels channels generate_go base IEntitlementsManager entitlements_manager generate_go base IEntitlementDataQueryable entitlement_data_queryable -generate_go base IERC721AQueryable erc721a_queryable generate_go base IPausable pausable -generate_go base IBanning banning generate_go base IWalletLink wallet_link generate_go base IRuleEntitlement rule_entitlement