Skip to content

Commit

Permalink
xchain eth balance (#679)
Browse files Browse the repository at this point in the history
E2E xchain support for eth balance checking. Note the getBalance method
on chains returns the balance of the native token so depending on the
chain it may be MATIC (e.g. polygon), etc.

testing:
- unit tests in typescript, go
- xchain IT tests
- e2e client-based tests for channels (stream node and client
entitlement checks) and spaces (enters through joinSpace and uses client
checks and xchain service)

Also added a few new unit tests to round out rule data evaluation
according to coderabbit's suggestion.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced new `ETHBALANCE` and `NATIVE_COIN_BALANCE` operation types
for entitlement checks, allowing evaluations based on Ethereum and
native coin wallet balances.
- Enhanced functionality for checking balances across linked wallets for
both Ethereum and native coins.
- Expanded the entitlement management system with new functions and
constants for improved balance checks.

- **Bug Fixes**
- Improved error handling during balance retrieval to prevent nil
address flags.

- **Tests**
- Significantly expanded test coverage for Ethereum and native coin
balance checks, incorporating various scenarios and configurations.
- Added comprehensive tests to validate entitlement checks based on both
Ethereum and native coin balances.

- **Documentation**
- Enhanced clarity and usability of the smart contract's ABI, with more
explicit input and output structures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
clemire authored Aug 12, 2024
1 parent 7a0b5bf commit 9daf077
Show file tree
Hide file tree
Showing 23 changed files with 1,444 additions and 41 deletions.
3 changes: 2 additions & 1 deletion contracts/src/spaces/entitlements/rule/IRuleEntitlement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ interface IRuleEntitlementBase {
ERC20,
ERC721,
ERC1155,
ISENTITLED
ISENTITLED,
NATIVE_COIN_BALANCE
}

// Enum for Operation oneof operation_clause
Expand Down
2 changes: 1 addition & 1 deletion core/contracts/base/deploy/mock_entitlement_gated.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions core/node/crypto/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (

var (
Eth_1 = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)
Eth_2 = new(big.Int).Mul(Eth_1, big.NewInt(2))
Eth_4 = new(big.Int).Mul(Eth_1, big.NewInt(4))
Eth_10 = new(big.Int).Exp(big.NewInt(10), big.NewInt(19), nil)
Eth_100 = new(big.Int).Exp(big.NewInt(10), big.NewInt(20), nil)
)
Expand Down
3 changes: 1 addition & 2 deletions core/xchain/client_simulator/client_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,7 @@ func (cs *clientSimulator) executeCheck(ctx context.Context, ruleData *deploy.IR
tx, err := gated.RequestEntitlementCheck(opts, big.NewInt(0), *ruleData)
log.Info("RequestEntitlementCheck called", "tx", tx, "err", err)
return tx, err
},
)
})

log.Info("Submitted entitlement check...")

Expand Down
90 changes: 83 additions & 7 deletions core/xchain/entitlement/check_operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,42 @@ func (e *Evaluator) evaluateCheckOperation(
// 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")
log.Error("Entitlement check: chain ID is nil for operation", "operation", op.CheckType.String())
return false, fmt.Errorf("evaluateCheckOperation: Chain ID is nil for operation %s", op.CheckType)
}

zeroAddress := common.Address{}
if op.ContractAddress == zeroAddress {
log.Info("Contract address is nil")
return false, fmt.Errorf("evaluateCheckOperation: Contract address is nil")
if op.CheckType != NATIVE_COIN_BALANCE && op.ContractAddress == zeroAddress {
log.Error("Entitlement check: contract address is nil for operation", "operation", op.CheckType.String())
return false, fmt.Errorf(
"evaluateCheckOperation: Contract address is nil for operation %s",
op.CheckType,
)
}

if op.CheckType == ERC20 || op.CheckType == ERC721 || op.CheckType == ERC1155 ||
op.CheckType == NATIVE_COIN_BALANCE {
if op.Threshold == nil {
log.Error("Entitlement check: threshold is nil for operation", "operation", op.CheckType.String())
return false, fmt.Errorf(
"evaluateCheckOperation: Threshold is nil for operation %s",
op.CheckType,
)
}
if op.Threshold.Sign() <= 0 {
log.Error(
"Entitlement check: threshold is nonpositive for operation",
"operation",
op.CheckType.String(),
"threshold",
op.Threshold.String(),
)
return false, fmt.Errorf(
"evaluateCheckOperation: Threshold %s is nonpositive for operation %s",
op.Threshold,
op.CheckType,
)
}
}

switch op.CheckType {
Expand All @@ -50,6 +79,8 @@ func (e *Evaluator) evaluateCheckOperation(
return e.evaluateErc721Operation(ctx, op, linkedWallets)
case ERC1155:
return e.evaluateErc1155Operation(ctx, op)
case NATIVE_COIN_BALANCE:
return e.evaluateNativeCoinBalanceOperation(ctx, op, linkedWallets)
case CheckNONE:
fallthrough
case MOCK:
Expand All @@ -59,7 +90,8 @@ func (e *Evaluator) evaluateCheckOperation(
}
}

func (e *Evaluator) evaluateMockOperation(ctx context.Context,
func (e *Evaluator) evaluateMockOperation(
ctx context.Context,
op *CheckOperation,
) (bool, error) {
delay := int(op.Threshold.Int64())
Expand Down Expand Up @@ -127,6 +159,48 @@ func (e *Evaluator) evaluateIsEntitledOperation(
return false, nil
}

// Check balance in decimals of native token
func (e *Evaluator) evaluateNativeCoinBalanceOperation(
ctx context.Context,
op *CheckOperation,
linkedWallets []common.Address,
) (bool, error) {
log := dlog.FromCtx(ctx).With("function", "evaluateNativeTokenBalanceOperation")
client, err := e.clients.Get(op.ChainID.Uint64())
if err != nil {
log.Error("Chain ID not found", "chainID", op.ChainID)
return false, fmt.Errorf("evaluateNativeTokenBalanceOperation: Chain ID %v not found", op.ChainID)
}

total := big.NewInt(0)
for _, wallet := range linkedWallets {
// Balance is returned as a representation of the balance according the denomination of the
// native token. The default decimals for most native tokens is 18, and we don't convert
// according to decimals here, but compare the threshold directly with the balance.
balance, err := client.BalanceAt(ctx, wallet, nil)
if err != nil {
log.Error("Failed to retrieve native token balance", "chain", op.ChainID, "error", err)
return false, err
}
total.Add(total, balance)

log.Info("Retrieved native token balance",
"balance", balance.String(),
"total", total.String(),
"threshold", op.Threshold.String(),
"chainID", op.ChainID.String(),
)

// Balance is a *big.Int
// Iteratively check if the total balance of evaluated wallets is greater than or equal to the
// threshold. Note threshold is always positive and total is non-negative.
if total.Cmp(op.Threshold) >= 0 {
return true, nil
}
}
return false, nil
}

func (e *Evaluator) evaluateErc20Operation(
ctx context.Context,

Expand Down Expand Up @@ -174,7 +248,8 @@ func (e *Evaluator) evaluateErc20Operation(

// Balance is a *big.Int
// Iteratively check if the total balance of evaluated wallets is greater than or equal to the threshold
if op.Threshold.Sign() > 0 && total.Sign() > 0 && total.Cmp(op.Threshold) >= 0 {
// Note threshold is always positive and total is non-negative.
if total.Cmp(op.Threshold) >= 0 {
return true, nil
}
}
Expand Down Expand Up @@ -226,6 +301,7 @@ func (e *Evaluator) evaluateErc721Operation(
// )

// Iteratively check if the total balance of evaluated wallets is greater than or equal to the threshold
// Note threshold is always positive and total is non-negative.
if total.Cmp(op.Threshold) >= 0 {
return true, nil
}
Expand Down
3 changes: 3 additions & 0 deletions core/xchain/entitlement/entitlement.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const (
ERC721
ERC1155
ISENTITLED
NATIVE_COIN_BALANCE
)

func (t CheckOperationType) String() string {
Expand All @@ -62,6 +63,8 @@ func (t CheckOperationType) String() string {
return "ERC1155"
case ISENTITLED:
return "ISENTITLED"
case NATIVE_COIN_BALANCE:
return "NATIVE_COIN_BALANCE"
default:
return "UNKNOWN"
}
Expand Down
Loading

0 comments on commit 9daf077

Please sign in to comment.