Skip to content

Commit

Permalink
feat: deploy user smart contracts (#1200)
Browse files Browse the repository at this point in the history
# Related Github tickets

- VolumeFi#159

# Background

Deploy any smart contract to any supported Paloma chain via the CLI
using the Pigeon Feed functionality for paying for message relays.

The PR adds a CLI interface to interact with user-defined smart
contracts:

- Upload new contract to Paloma. The contract will be available to be
deploy in any of our target chains.
- List contracts. Users can see their contracts and deployment status,
as well as target addresses when successfully deployed.
- Remove contract. Delete the contract from Paloma.
- Deploy contract. Trigger deployment of a previously uploaded smart
contract to any of our target chains.

# Testing completed

- [x] test coverage exists or has been added/updated
- [x] tested in a private testnet

# Breaking changes

- [x] I have checked my code for breaking changes
- [x] If there are breaking changes, there is a supporting migration.
  • Loading branch information
maharifu authored Aug 22, 2024
1 parent 8310da9 commit 5baf5f2
Show file tree
Hide file tree
Showing 54 changed files with 6,449 additions and 915 deletions.
2 changes: 2 additions & 0 deletions proto/palomachain/paloma/evm/chain_info.proto
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ message ChainInfo {
RelayWeights relayWeights = 13;

string feeManagerAddr = 14;

string smartContractDeployerAddr = 15;
}

message SmartContract {
Expand Down
17 changes: 17 additions & 0 deletions proto/palomachain/paloma/evm/query.proto
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
syntax = "proto3";
package palomachain.paloma.evm;

import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "palomachain/paloma/evm/params.proto";
import "palomachain/paloma/evm/turnstone.proto";
import "palomachain/paloma/evm/chain_info.proto";
import "palomachain/paloma/evm/user_smart_contract.proto";

option go_package = "github.com/palomachain/paloma/x/evm/types";

Expand Down Expand Up @@ -41,6 +43,13 @@ service Query {
option (google.api.http).get =
"/palomachain/paloma/evm/query_get_smart_contract_deployments";
}

// Queries a list of user smart contracts
rpc QueryUserSmartContracts(QueryUserSmartContractsRequest)
returns (QueryUserSmartContractsResponse) {
option (google.api.http).get =
"/palomachain/paloma/evm/query_user_smart_contracts/{val_address}";
}
}

// QueryParamsRequest is request type for the Query/Params RPC method.
Expand Down Expand Up @@ -76,3 +85,11 @@ message QueryGetSmartContractDeploymentsRequest {}
message QueryGetSmartContractDeploymentsResponse {
repeated SmartContractDeployment deployments = 1;
}

message QueryUserSmartContractsRequest {
string val_address = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
}

message QueryUserSmartContractsResponse {
repeated UserSmartContract contracts = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
syntax = "proto3";
package palomachain.paloma.evm;

import "gogoproto/gogo.proto";

option go_package = "github.com/palomachain/paloma/x/evm/types";

message SetSmartContractDeployersProposal {
message Deployer {
string chainReferenceID = 1;
string contractAddress = 2;
}

string title = 1;
string summary = 2;
repeated Deployer deployers = 3 [ (gogoproto.nullable) = false ];
}
30 changes: 24 additions & 6 deletions proto/palomachain/paloma/evm/turnstone.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ message Valset {
uint64 valsetID = 3;
}

message Fees {
uint64 relayerFee = 1;
uint64 communityFee = 2;
uint64 securityFee = 3;
}

message SubmitLogicCall {
message ExecutionRequirements { bool enforceMEVRelay = 1; }
message Fees {
uint64 relayerFee = 1;
uint64 communityFee = 2;
uint64 securityFee = 3;
}
string hexContractAddress = 1;
bytes abi = 2;
bytes payload = 3;
Expand All @@ -47,6 +48,19 @@ message UploadSmartContract {
uint32 retries = 5;
}

message UploadUserSmartContract {
bytes bytecode = 1;
string deployerAddress = 2;
int64 deadline = 3;

bytes senderAddress = 4;
int64 blockHeight = 5;
uint64 id = 6;
uint32 retries = 7;

Fees fees = 8 [ (gogoproto.nullable) = true ];
}

message Message {
// Previous definitions no longer in use.
reserved 8;
Expand All @@ -59,6 +73,7 @@ message Message {
SubmitLogicCall submitLogicCall = 3;
UpdateValset updateValset = 4;
UploadSmartContract uploadSmartContract = 5;
UploadUserSmartContract uploadUserSmartContract = 11;
}

string compassAddr = 6;
Expand All @@ -76,7 +91,10 @@ message Message {
string assigneeRemoteAddress = 10;
}

message TxExecutedProof { bytes serializedTX = 1; }
message TxExecutedProof {
bytes serializedTX = 1;
bytes serializedReceipt = 2 [ (gogoproto.nullable) = true ];
}

message SmartContractExecutionErrorProof { string errorMessage = 1; }

Expand Down
65 changes: 50 additions & 15 deletions proto/palomachain/paloma/evm/tx.proto
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
syntax = "proto3";
package palomachain.paloma.evm;

import "gogoproto/gogo.proto";
import "cosmos/msg/v1/msg.proto";
import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/empty.proto";
import "palomachain/paloma/valset/common.proto";

option go_package = "github.com/palomachain/paloma/x/evm/types";

service Msg {
rpc DeployNewSmartContract(MsgDeployNewSmartContractRequest)
returns (DeployNewSmartContractResponse);
rpc RemoveSmartContractDeployment(MsgRemoveSmartContractDeploymentRequest)
returns (RemoveSmartContractDeploymentResponse);
}

message MsgDeployNewSmartContractRequest {
option (cosmos.msg.v1.signer) = "metadata";
reserved 1;
reserved "creator";
string title = 2;
string description = 3;
// ===== User Smart Contracts =====
rpc UploadUserSmartContract(MsgUploadUserSmartContractRequest)
returns (MsgUploadUserSmartContractResponse);

string abiJSON = 4;
string bytecodeHex = 5;
rpc RemoveUserSmartContract(MsgRemoveUserSmartContractRequest)
returns (google.protobuf.Empty);

palomachain.paloma.valset.MsgMetadata metadata = 6
[ (gogoproto.nullable) = false ];
rpc DeployUserSmartContract(MsgDeployUserSmartContractRequest)
returns (MsgDeployUserSmartContractResponse);
}
message DeployNewSmartContractResponse {}

message MsgRemoveSmartContractDeploymentRequest {
option (cosmos.msg.v1.signer) = "metadata";
Expand All @@ -39,3 +35,42 @@ message MsgRemoveSmartContractDeploymentRequest {
[ (gogoproto.nullable) = false ];
}
message RemoveSmartContractDeploymentResponse {}

message MsgUploadUserSmartContractRequest {
option (cosmos.msg.v1.signer) = "metadata";

palomachain.paloma.valset.MsgMetadata metadata = 1
[ (gogoproto.nullable) = false ];

string title = 2;
string abi_json = 3;
string bytecode = 4;
string constructor_input = 5;
}

message MsgUploadUserSmartContractResponse {
uint64 id = 1;
}

message MsgRemoveUserSmartContractRequest {
option (cosmos.msg.v1.signer) = "metadata";

palomachain.paloma.valset.MsgMetadata metadata = 1
[ (gogoproto.nullable) = false ];

uint64 id = 2;
}

message MsgDeployUserSmartContractRequest {
option (cosmos.msg.v1.signer) = "metadata";

palomachain.paloma.valset.MsgMetadata metadata = 1
[ (gogoproto.nullable) = false ];

uint64 id = 2;
string target_chain = 3;
}

message MsgDeployUserSmartContractResponse {
uint64 msg_id = 1;
}
35 changes: 35 additions & 0 deletions proto/palomachain/paloma/evm/user_smart_contract.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
syntax = "proto3";
package palomachain.paloma.evm;

import "cosmos_proto/cosmos.proto";

option go_package = "github.com/palomachain/paloma/x/evm/types";

// UserSmartContract defines user-uploaded smart contracts
// We keep them in storage, so users can decide to deploy them anywhere
message UserSmartContract {
message Deployment {
enum Status {
PENDING = 0;
IN_FLIGHT = 1;
ACTIVE = 2;
ERROR = 3;
}

string chain_reference_id = 1;
Status status = 2;
string address = 3;
int64 created_at_block_height = 4;
int64 updated_at_block_height = 5;
}

string author = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
uint64 id = 2;
string title = 3;
string abi_json = 4;
string bytecode = 5;
string constructor_input = 6;
repeated Deployment deployments = 7;
int64 created_at_block_height = 8;
int64 updated_at_block_height = 9;
}
10 changes: 10 additions & 0 deletions util/blocks/blocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package blocks

const (
// dailyBlocks estimated with a block time of 1.5s
dailyHeight = 57_600
DailyHeight = dailyHeight
WeeklyHeight = dailyHeight * 7
MonthlyHeight = dailyHeight * 30
YearlyHeight = dailyHeight * 365
)
17 changes: 10 additions & 7 deletions x/consensus/keeper/estimate.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,14 @@ func (k Keeper) checkAndProcessEstimatedMessage(ctx context.Context,
return fmt.Errorf("failed to set elected gas estimate: %w", err)
}

if err := k.checkAndProcessEstimatedSubmitLogicCall(ctx, msg, q, estimate); err != nil {
if err := k.checkAndProcessEstimatedFeePayer(ctx, msg, q, estimate); err != nil {
return fmt.Errorf("failed to process estimated submit logic call: %w", err)
}

return nil
}

func (k Keeper) checkAndProcessEstimatedSubmitLogicCall(
func (k Keeper) checkAndProcessEstimatedFeePayer(
ctx context.Context,
msg types.QueuedSignedMessageI,
q consensus.Queuer,
Expand All @@ -117,21 +117,24 @@ func (k Keeper) checkAndProcessEstimatedSubmitLogicCall(
if err != nil {
return fmt.Errorf("failed to convert message to evm message: %w", err)
}
action, ok := m.Action.(*evmtypes.Message_SubmitLogicCall)
action, ok := m.Action.(evmtypes.FeePayer)
if !ok {
// Skip messages that are not SubmitLogicCall
// Skip messages that do not contain fees
return nil
}

valAddr, err := sdk.ValAddressFromBech32(m.GetAssignee())
if err != nil {
return fmt.Errorf("failed to parse validator address: %w", err)
}

fees, err := k.calculateFeesForEstimate(ctx, valAddr, m.GetChainReferenceID(), estimate)
if err != nil {
return fmt.Errorf("failed to calculate fees for estimate: %w", err)
}
action.SubmitLogicCall.Fees = fees

action.SetFees(fees)

_, err = q.Put(ctx, m, &consensus.PutOptions{
MsgIDToReplace: msg.GetId(),
})
Expand All @@ -144,8 +147,8 @@ func (k Keeper) calculateFeesForEstimate(
relayer sdk.ValAddress,
chainReferenceID string,
estimate uint64,
) (*evmtypes.SubmitLogicCall_Fees, error) {
fees := &evmtypes.SubmitLogicCall_Fees{}
) (*evmtypes.Fees, error) {
fees := &evmtypes.Fees{}
multiplicators, err := k.feeProvider.GetCombinedFeesForRelay(ctx, relayer, chainReferenceID)
if err != nil {
return nil, fmt.Errorf("failed to get fee settings: %w", err)
Expand Down
26 changes: 26 additions & 0 deletions x/consensus/keeper/estimate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,32 @@ func Test_CheckAndProcessEstimatedMessages(t *testing.T) {
return true
},
},
{
name: "Upload user smart contract message",
msg: &evmtypes.Message{
TurnstoneID: "abc",
ChainReferenceID: chainReferenceID,
Assignee: validators[0].Address.String(),
Action: &evmtypes.Message_UploadUserSmartContract{
UploadUserSmartContract: &evmtypes.UploadUserSmartContract{},
},
},
slcCheck: func(m *evmtypes.Message, r *require.Assertions, expected bool) bool {
usc := m.GetUploadUserSmartContract()
if usc == nil {
return expected
}
if !expected {
return usc.Fees == nil
}

r.NotNil(usc.Fees)
r.Equal(uint64(31500), usc.Fees.RelayerFee, "relayer fee: got %d", usc.Fees.RelayerFee)
r.Equal(uint64(9450), usc.Fees.CommunityFee, "community fee: got %d", usc.Fees.CommunityFee)
r.Equal(uint64(315), usc.Fees.SecurityFee, "security fee: got %d", usc.Fees.SecurityFee)
return true
},
},
}

for _, tc := range tt {
Expand Down
2 changes: 2 additions & 0 deletions x/evm/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command {

cmd.AddCommand(CmdQueryGetSmartContractDeployments())

cmd.AddCommand(CmdQueryUserSmartContracts())

return cmd
}
Loading

0 comments on commit 5baf5f2

Please sign in to comment.