Skip to content

Commit

Permalink
Add support for creating allocations via Filplus EVM Client Contract
Browse files Browse the repository at this point in the history
  • Loading branch information
kacperzuk-neti committed Oct 2, 2024
1 parent eff5121 commit 2cc97f8
Show file tree
Hide file tree
Showing 6 changed files with 488 additions and 42 deletions.
34 changes: 27 additions & 7 deletions cmd/boost/direct_deal.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ var directDealAllocate = &cli.Command{
Usage: "automatic yes to prompts; assume 'yes' as answer to all prompts and run non-interactively",
Aliases: []string{"y", "yes"},
},
&cli.StringFlag{
Name: "evm-client-contract",
Usage: "address of EVM contract to spend DataCap from",
},
},
Before: before,
Action: func(cctx *cli.Context) error {
Expand All @@ -101,6 +105,7 @@ var directDealAllocate = &cli.Command{
pieceFile := cctx.String("piece-file")
miners := cctx.StringSlice("miner")
pinfos := cctx.StringSlice("piece-info")

if pieceFile == "" && len(pinfos) < 1 {
return fmt.Errorf("must provide at least one --piece-info or use --piece-file")
}
Expand Down Expand Up @@ -256,13 +261,28 @@ var directDealAllocate = &cli.Command{

log.Debugw("selected wallet", "wallet", walletAddr)

msgs, err := util.CreateAllocationMsg(ctx, gapi, pieceInfos, walletAddr, cctx.Int("batch-size"))

if err != nil {
return err
var msgs []*types.Message
var allocationsAddr address.Address
evmContract := cctx.String("evm-client-contract")
if evmContract != "" {
evmContractAddr, err := address.NewFromString(evmContract)
if err != nil {
return err
}
allocationsAddr = evmContractAddr
msgs, err = util.CreateAllocationViaEVMMsg(ctx, gapi, pieceInfos, walletAddr, evmContractAddr, cctx.Int("batch-size"))
if err != nil {
return err
}
} else {
allocationsAddr = walletAddr
msgs, err = util.CreateAllocationMsg(ctx, gapi, pieceInfos, walletAddr, cctx.Int("batch-size"))
if err != nil {
return err
}
}

oldallocations, err := gapi.StateGetAllocations(ctx, walletAddr, types.EmptyTSK)
oldallocations, err := gapi.StateGetAllocations(ctx, allocationsAddr, types.EmptyTSK)
if err != nil {
return fmt.Errorf("failed to get allocations: %w", err)
}
Expand All @@ -287,7 +307,7 @@ var directDealAllocate = &cli.Command{
mcidStr = append(mcidStr, c.String())
}

log.Infow("submitted data cap allocation message[s]", mcidStr)
log.Infow("submitted data cap allocation message[s]", "mcidStr", mcidStr)
log.Info("waiting for message to be included in a block")

// wait for msgs to get mined into a block
Expand Down Expand Up @@ -318,7 +338,7 @@ var directDealAllocate = &cli.Command{
return nil
}

newallocations, err := gapi.StateGetAllocations(ctx, walletAddr, types.EmptyTSK)
newallocations, err := gapi.StateGetAllocations(ctx, allocationsAddr, types.EmptyTSK)
if err != nil {
return fmt.Errorf("failed to get allocations: %w", err)
}
Expand Down
272 changes: 272 additions & 0 deletions cmd/boost/util/evm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
package util

import (
"bytes"
"context"
"fmt"
"strings"

mbig "math/big"

eabi "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/builtin"
verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
)

// https://github.com/fidlabs/contract-metaallocator/blob/main/src/Client.sol
const contractABI = `[
{
"type": "function",
"name": "allowances",
"inputs": [
{
"name": "client",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "allowance",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "transfer",
"inputs": [
{
"name": "params",
"type": "tuple",
"internalType": "struct DataCapTypes.TransferParams",
"components": [
{
"name": "to",
"type": "tuple",
"internalType": "struct CommonTypes.FilAddress",
"components": [
{
"name": "data",
"type": "bytes",
"internalType": "bytes"
}
]
},
{
"name": "amount",
"type": "tuple",
"internalType": "struct CommonTypes.BigInt",
"components": [
{
"name": "val",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "neg",
"type": "bool",
"internalType": "bool"
}
]
},
{
"name": "operator_data",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
]`

func getAddressAllowanceOnContract(ctx context.Context, api api.Gateway, wallet address.Address, contract address.Address) (*big.Int, error) {
// Parse the contract ABI
parsedABI, err := eabi.JSON(strings.NewReader(contractABI))
if err != nil {
return nil, fmt.Errorf("Failed to parse contract ABI: %w", err)
}

// Convert from Filecoin to Eth Address
walletId, err := api.StateLookupID(ctx, wallet, types.EmptyTSK)
if err != nil {
return nil, err
}
walletEvm, err := ethtypes.EthAddressFromFilecoinAddress(walletId)
if err != nil {
return nil, err
}

// Prepare EVM calldata
calldata, err := parsedABI.Pack("allowances", walletEvm)
if err != nil {
return nil, fmt.Errorf("failed to serialize parameters to check allowances: %w", err)
}

// Encode EVM calldata as Message parameters
allowanceParam := abi.CborBytes(calldata)
allowanceParams, err := actors.SerializeParams(&allowanceParam)
if err != nil {
return nil, fmt.Errorf("failed to serialize params: %w", err)
}

// Read allowance from the contract
allowanceMsg := &types.Message{
To: contract,
From: wallet,
Method: builtin.MethodsEVM.InvokeContract,
Params: allowanceParams,
Value: big.Zero(),
}
result, err := api.StateCall(ctx, allowanceMsg, types.EmptyTSK)
if err != nil {
return nil, err
}

// Decode return value (cbor -> evm ABI -> math/big Int -> filecoin big Int)
var decodedReturn abi.CborBytes
decodedReturn.UnmarshalCBOR(bytes.NewReader(result.MsgRct.Return))
retValue, err := parsedABI.Unpack("allowances", decodedReturn)
if err != nil {
return nil, err
}
allowance, err := big.FromString(retValue[0].(*mbig.Int).String())
return &allowance, err
}

func buildTransferViaEVMParams(amount *big.Int, receiverParams []byte) ([]byte, error) {
// Parse the contract's ABI
parsedABI, err := eabi.JSON(strings.NewReader(contractABI))
if err != nil {
return nil, fmt.Errorf("Failed to parse contract ABI: %w", err)
}

// convert amount from Filecoin big.Int to math/big Int
var amountMbig mbig.Int
_, success := amountMbig.SetString(amount.String(), 10)
if !success {
return nil, fmt.Errorf("failed to serialize the amount")
}

// build calldata
calldata, err := parsedABI.Pack(
"transfer",
TransferParams{
To: FilAddress{Data: builtin.VerifiedRegistryActorAddr.Bytes()},
Amount: BigInt{
Neg: amount.LessThan(big.Zero()),
Val: amountMbig.Bytes(),
},
OperatorData: receiverParams,
})

transferParam := abi.CborBytes(calldata)
transferParams, err := actors.SerializeParams(&transferParam)
if err != nil {
return nil, fmt.Errorf("failed to serialize params: %w", err)
}
return transferParams, nil
}

func CreateAllocationViaEVMMsg(ctx context.Context, api api.Gateway, infos []PieceInfos, wallet address.Address, contract address.Address, batchSize int) ([]*types.Message, error) {
// Create allocation requests
rDataCap, allocationRequests, err := CreateAllocationRequests(ctx, api, infos)
if err != nil {
return nil, err
}

// Get datacap balance of the contract
aDataCap, err := api.StateVerifiedClientStatus(ctx, contract, types.EmptyTSK)
if err != nil {
return nil, err
}

if aDataCap == nil {
return nil, fmt.Errorf("contract %s does not have any datacap", wallet)
}

// Check that we have enough data cap to make the allocation
if rDataCap.GreaterThan(big.NewInt(aDataCap.Int64())) {
return nil, fmt.Errorf("requested datacap %s is greater then the available contract datacap %s", rDataCap, aDataCap)
}

// Get datacap allowance of the wallet
allowance, err := getAddressAllowanceOnContract(ctx, api, wallet, contract)
if err != nil {
return nil, err
}

// Check that we have enough data cap to make the allocation
if rDataCap.GreaterThan(*allowance) {
return nil, fmt.Errorf("requested datacap %s is greater then the available datacap allowance %s", rDataCap, allowance)
}

// Batch allocationRequests to create message
var messages []*types.Message
for i := 0; i < len(allocationRequests); i += batchSize {
end := i + batchSize
if end > len(allocationRequests) {
end = len(allocationRequests)
}
batch := allocationRequests[i:end]
arequest := &verifreg9.AllocationRequests{
Allocations: batch,
}
bDataCap := big.NewInt(0)
for _, bd := range batch {
bDataCap.Add(big.NewInt(int64(bd.Size)).Int, bDataCap.Int)
}

receiverParams, err := actors.SerializeParams(arequest)
if err != nil {
return nil, fmt.Errorf("failed to serialize the parameters: %w", err)
}

amount := big.Mul(bDataCap, builtin.TokenPrecision)

transferParams, error := buildTransferViaEVMParams(&amount, receiverParams)
if error != nil {
return nil, error
}

msg := &types.Message{
To: contract,
From: wallet,
Method: builtin.MethodsEVM.InvokeContract,
Params: transferParams,
Value: big.Zero(),
}
messages = append(messages, msg)
}
return messages, nil
}

// https://github.com/filecoin-project/filecoin-solidity/blob/f655709ab02fcf4b7859f47149f1e0cbfa975041/contracts/v0.8/types/CommonTypes.sol#L86
type FilAddress struct {
Data []byte
}

// https://github.com/filecoin-project/filecoin-solidity/blob/f655709ab02fcf4b7859f47149f1e0cbfa975041/contracts/v0.8/types/CommonTypes.sol#L80
type BigInt struct {
Val []byte
Neg bool
}

// https://github.com/filecoin-project/filecoin-solidity/blob/f655709ab02fcf4b7859f47149f1e0cbfa975041/contracts/v0.8/types/DataCapTypes.sol#L52
type TransferParams struct {
To FilAddress
Amount BigInt
OperatorData []byte
}
27 changes: 16 additions & 11 deletions cmd/boost/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,20 @@ import (
"github.com/manifoldco/promptui"
)

func CreateAllocationMsg(ctx context.Context, api api.Gateway, infos []PieceInfos, wallet address.Address, batchSize int) ([]*types.Message, error) {

func CreateAllocationRequests(ctx context.Context, api api.Gateway, infos []PieceInfos) (*big.Int, []verifreg9.AllocationRequest, error) {
var allocationRequests []verifreg9.AllocationRequest
rDataCap := big.NewInt(0)
head, err := api.ChainHead(ctx)
if err != nil {
return nil, err
return nil, nil, err
}

rDataCap := big.NewInt(0)

// Create allocation requests
var allocationRequests []verifreg9.AllocationRequest
for _, info := range infos {
minfo, err := api.StateMinerInfo(ctx, info.MinerAddr, types.EmptyTSK)
if err != nil {
return nil, err
return nil, nil, err
}
if uint64(minfo.SectorSize) < uint64(info.Size) {
return nil, fmt.Errorf("specified piece size %d is bigger than miner's sector size %s", info.Size, minfo.SectorSize.String())
return nil, nil, fmt.Errorf("specified piece size %d is bigger than miner's sector size %s", info.Size, minfo.SectorSize.String())
}
allocationRequests = append(allocationRequests, verifreg9.AllocationRequest{
Provider: info.Miner,
Expand All @@ -51,6 +47,15 @@ func CreateAllocationMsg(ctx context.Context, api api.Gateway, infos []PieceInfo
})
rDataCap.Add(big.NewInt(info.Size).Int, rDataCap.Int)
}
return &rDataCap, allocationRequests, nil
}

func CreateAllocationMsg(ctx context.Context, api api.Gateway, infos []PieceInfos, wallet address.Address, batchSize int) ([]*types.Message, error) {
// Create allocation requests
rDataCap, allocationRequests, err := CreateAllocationRequests(ctx, api, infos)
if err != nil {
return nil, err
}

// Get datacap balance
aDataCap, err := api.StateVerifiedClientStatus(ctx, wallet, types.EmptyTSK)
Expand Down Expand Up @@ -85,7 +90,7 @@ func CreateAllocationMsg(ctx context.Context, api api.Gateway, infos []PieceInfo

receiverParams, err := actors.SerializeParams(arequest)
if err != nil {
return nil, fmt.Errorf("failed to seralize the parameters: %w", err)
return nil, fmt.Errorf("failed to serialize the parameters: %w", err)
}

transferParams, err := actors.SerializeParams(&datacap.TransferParams{
Expand Down
Loading

0 comments on commit 2cc97f8

Please sign in to comment.