Skip to content

Commit

Permalink
feat(services): rewrite settle order with eip4337
Browse files Browse the repository at this point in the history
  • Loading branch information
chibie committed Dec 9, 2023
1 parent 3f31ce8 commit be9e31e
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 55 deletions.
2 changes: 1 addition & 1 deletion controllers/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ func (ctrl *ProviderController) FulfillOrder(ctx *gin.Context) {
}

// Settle order or fail silently
err = ctrl.orderService.SettleOrder(ctx, nil, orderID)
err = ctrl.orderService.SettleOrder(ctx, orderID)
if err != nil {
logger.Errorf("FulfillOrder.SettleOrder: %v", err)
}
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
Expand Down
6 changes: 6 additions & 0 deletions services/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ func (s *IndexerService) IndexERC20Transfer(ctx context.Context, client types.RP
_, err = paymentOrder.
Update().
SetAmountPaid(amountPaid).
SetTxHash(vLog.TxHash.Hex()).
Save(ctx)
if err != nil {
logger.Errorf("IndexERC20Transfer.db: %v", err)
Expand Down Expand Up @@ -283,6 +284,7 @@ func (s *IndexerService) IndexERC20Transfer(ctx context.Context, client types.RP
_, err := paymentOrder.
Update().
SetStatus(paymentorder.StatusReverted).
SetTxHash(vLog.TxHash.Hex()).
Save(ctx)
if err != nil {
logger.Errorf("IndexERC20Transfer.db: %v", err)
Expand Down Expand Up @@ -805,6 +807,7 @@ func (s *IndexerService) createLockPaymentOrder(ctx context.Context, client type
Rate: rate,
Label: utils.Byte32ToString(deposit.Label),
BlockNumber: int64(deposit.Raw.BlockNumber),
TxHash: deposit.Raw.TxHash.Hex(),
Institution: utils.Byte32ToString(deposit.InstitutionCode),
AccountIdentifier: recipient.AccountIdentifier,
AccountName: recipient.AccountName,
Expand Down Expand Up @@ -842,6 +845,7 @@ func (s *IndexerService) createLockPaymentOrder(ctx context.Context, client type
SetLabel(lockPaymentOrder.Label).
SetOrderPercent(decimal.NewFromInt(100)).
SetBlockNumber(lockPaymentOrder.BlockNumber).
SetTxHash(lockPaymentOrder.TxHash).
SetInstitution(lockPaymentOrder.Institution).
SetAccountIdentifier(lockPaymentOrder.AccountIdentifier).
SetAccountName(lockPaymentOrder.AccountName).
Expand Down Expand Up @@ -952,6 +956,7 @@ func (s *IndexerService) splitLockPaymentOrder(ctx context.Context, lockPaymentO
SetRate(lockPaymentOrder.Rate).
SetOrderPercent(orderPercent).
SetBlockNumber(lockPaymentOrder.BlockNumber).
SetTxHash(lockPaymentOrder.TxHash).
SetInstitution(lockPaymentOrder.Institution).
SetAccountIdentifier(lockPaymentOrder.AccountIdentifier).
SetAccountName(lockPaymentOrder.AccountName).
Expand Down Expand Up @@ -1000,6 +1005,7 @@ func (s *IndexerService) splitLockPaymentOrder(ctx context.Context, lockPaymentO
SetAmount(amountToSplit.Div(lockPaymentOrder.Rate)).
SetRate(lockPaymentOrder.Rate).
SetBlockNumber(lockPaymentOrder.BlockNumber).
SetTxHash(lockPaymentOrder.TxHash).
SetInstitution(lockPaymentOrder.Institution).
SetAccountIdentifier(lockPaymentOrder.AccountIdentifier).
SetAccountName(lockPaymentOrder.AccountName).
Expand Down
169 changes: 117 additions & 52 deletions services/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ func (s *OrderService) RevertOrder(ctx context.Context, order *ent.PaymentOrder,
}

// SettleOrder settles a payment order on-chain.
func (s *OrderService) SettleOrder(ctx context.Context, client types.RPCClient, orderID uuid.UUID) error {
func (s *OrderService) SettleOrder(ctx context.Context, orderID uuid.UUID) error {
var err error

// Fetch payment order from db
Expand All @@ -300,68 +300,43 @@ func (s *OrderService) SettleOrder(ctx context.Context, client types.RPCClient,
return fmt.Errorf("failed to fetch lock order: %w", err)
}

// Fetch provider address from db
token, err := db.Client.ProviderOrderToken.
Query().
Where(
providerordertoken.SymbolEQ(order.Edges.Token.Symbol),
providerordertoken.HasProviderWith(
providerprofile.IDEQ(order.Edges.Provider.ID),
),
).
Only(ctx)
// Get default userOperation
userOperation, err := utils.InitializeUserOperation(
ctx, nil, order.Edges.Token.Edges.Network.RPCEndpoint, CryptoConf.AggregatorSmartAccount, CryptoConf.AggregatorSmartAccountSalt,
)
if err != nil {
return fmt.Errorf("failed to fetch provider order token: %w", err)
return fmt.Errorf("SettleOrder.initializeUserOperation: %w", err)
}

var providerAddress string
for _, addr := range token.Addresses {
if addr.Network == order.Edges.Token.Edges.Network.Identifier {
providerAddress = addr.Address
break
}
}

if providerAddress == "" {
return fmt.Errorf("failed to fetch provider address: %w", err)
}

// Connect to RPC endpoint
if client == nil {
client, err = types.NewEthClient(order.Edges.Token.Edges.Network.RPCEndpoint)
if err != nil {
return fmt.Errorf("failed to connect to RPC client: %w", err)
}
// Create calldata
calldata, err := s.executeBatchSettleCallData(ctx, order)
if err != nil {
return fmt.Errorf("SettleOrder.settleCallData: %w", err)
}
userOperation.CallData = calldata

// Initialize paycrest order contract
orderContract, err := contracts.NewPaycrestOrder(OrderConf.PaycrestOrderContractAddress, client.(bind.ContractBackend))
// Sponsor user operation.
// This will populate the following fields in userOperation: PaymasterAndData, PreVerificationGas, VerificationGasLimit, CallGasLimit
err = utils.SponsorUserOperation(userOperation, "payg")
if err != nil {
return fmt.Errorf("failed to initialize paycrest order contract: %w", err)
return fmt.Errorf("SettleOrder.sponsorUserOperation: %w", err)
}

orderPercent, _ := order.OrderPercent.Float64()
// Sign user operation
_ = utils.SignUserOperation(userOperation)

// Settle order
tx, err := orderContract.Settle(
nil,
utils.StringToByte32(order.ID.String()),
utils.StringToByte32(order.OrderID),
utils.StringToByte32(order.Label),
nil, // TODO: remove validators input from contract
common.HexToAddress(providerAddress),
uint64(orderPercent),
order.Edges.Provider.IsPartner,
)
// Send user operation
userOpTxHash, err := utils.SendUserOperation(userOperation)
if err != nil {
return fmt.Errorf("failed to settle order: %w", err)
return fmt.Errorf("SettleOrder.sendUserOperation: %w", err)
}

// Update status of lock order
_, err = order.Update().
SetTxHash(tx.Hash().Hex()).
SetTxHash(userOpTxHash).
Save(ctx)
if err != nil {
return fmt.Errorf("failed to update payment order: %w", err)
return fmt.Errorf("SettleOrder.updateTxHash: %w", err)
}

return nil
Expand Down Expand Up @@ -587,7 +562,7 @@ func (s *OrderService) executeBatchRefundCallData(order *ent.LockPaymentOrder) (
return nil, fmt.Errorf("executeBatchRefundCallData.simpleAccountABI: %w", err)
}

executeBatchCreateOrderCallData, err := simpleAccountABI.Pack(
executeBatchRefundCallData, err := simpleAccountABI.Pack(
"executeBatch",
[]common.Address{
common.HexToAddress(order.Edges.Token.ContractAddress),
Expand All @@ -599,18 +574,17 @@ func (s *OrderService) executeBatchRefundCallData(order *ent.LockPaymentOrder) (
return nil, fmt.Errorf("executeBatchRefundCallData: %w", err)
}

return executeBatchCreateOrderCallData, nil
return executeBatchRefundCallData, nil
}

// refundCallData creates the data for the refund method
func (s *OrderService) refundCallData(orderId, label string) ([]byte, error) {
// Refund ABI
paycrestOrderABI, err := abi.JSON(strings.NewReader(contracts.PaycrestOrderMetaData.ABI))
if err != nil {
return nil, fmt.Errorf("failed to parse PaycrestOrder ABI: %w", err)
}

// Generate call data for refund, orderID, and label should be byte32
// Generate calldata for refund, orderID, and label should be byte32
data, err := paycrestOrderABI.Pack(
"refund",
utils.StringToByte32(orderId),
Expand All @@ -624,6 +598,97 @@ func (s *OrderService) refundCallData(orderId, label string) ([]byte, error) {
return data, nil
}

// executeBatchSettleCallData creates the settle calldata for the execute batch method in the smart account.
func (s *OrderService) executeBatchSettleCallData(ctx context.Context, order *ent.LockPaymentOrder) ([]byte, error) {
// Create approve data for paycrest order contract
approvePaycrestData, err := s.approveCallData(
OrderConf.PaycrestOrderContractAddress,
utils.ToSubunit(order.Amount, order.Edges.Token.Decimals),
)
if err != nil {
return nil, fmt.Errorf("executeBatchSettleCallData.approveOrderContract: %w", err)
}

// Create settle data
settleData, err := s.settleCallData(ctx, order)
if err != nil {
return nil, fmt.Errorf("executeBatchSettleCallData.refundData: %w", err)
}

simpleAccountABI, err := abi.JSON(strings.NewReader(contracts.SimpleAccountMetaData.ABI))
if err != nil {
return nil, fmt.Errorf("executeBatchSettleCallData.simpleAccountABI: %w", err)
}

executeBatchSettleCallData, err := simpleAccountABI.Pack(
"executeBatch",
[]common.Address{
common.HexToAddress(order.Edges.Token.ContractAddress),
OrderConf.PaycrestOrderContractAddress,
},
[][]byte{approvePaycrestData, settleData},
)
if err != nil {
return nil, fmt.Errorf("executeBatchSettledCallData: %w", err)
}

return executeBatchSettleCallData, nil
}

// settleCallData creates the data for the settle method in the paycrest order contract
func (s *OrderService) settleCallData(ctx context.Context, order *ent.LockPaymentOrder) ([]byte, error) {
paycrestOrderABI, err := abi.JSON(strings.NewReader(contracts.PaycrestOrderMetaData.ABI))
if err != nil {
return nil, fmt.Errorf("failed to parse PaycrestOrder ABI: %w", err)
}

// Fetch provider address from db
token, err := db.Client.ProviderOrderToken.
Query().
Where(
providerordertoken.SymbolEQ(order.Edges.Token.Symbol),
providerordertoken.HasProviderWith(
providerprofile.IDEQ(order.Edges.Provider.ID),
),
).
Only(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch provider order token: %w", err)
}

var providerAddress string
for _, addr := range token.Addresses {
if addr.Network == order.Edges.Token.Edges.Network.Identifier {
providerAddress = addr.Address
break
}
}

if providerAddress == "" {
return nil, fmt.Errorf("failed to fetch provider address: %w", err)
}

orderPercent, _ := order.OrderPercent.Float64()

// Generate calldata for settlement
data, err := paycrestOrderABI.Pack(
"settle",
utils.StringToByte32(order.ID.String()),
utils.StringToByte32(order.OrderID),
utils.StringToByte32(order.Label),
nil, // TODO: remove validators input from contract
common.HexToAddress(providerAddress),
uint64(orderPercent),
order.Edges.Provider.IsPartner,
)

if err != nil {
return nil, fmt.Errorf("failed to pack settle ABI: %w", err)
}

return data, nil
}

// encryptOrderRecipient encrypts the recipient details
func (s *OrderService) encryptOrderRecipient(recipient *ent.PaymentOrderRecipient) (string, error) {
message := struct {
Expand Down
1 change: 1 addition & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ type LockPaymentOrderFields struct {
Rate decimal.Decimal
Label string
BlockNumber int64
TxHash string
Institution string
AccountIdentifier string
AccountName string
Expand Down

0 comments on commit be9e31e

Please sign in to comment.