Skip to content

Commit

Permalink
feat: fee_balancer lambda
Browse files Browse the repository at this point in the history
  • Loading branch information
eugypalu committed Nov 18, 2024
1 parent 5cdbb52 commit 87e0a82
Show file tree
Hide file tree
Showing 8 changed files with 492 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,18 @@ cairo/kakarot-ssj/cache/
cairo/kakarot-ssj/scripts/libcairo_native_runtime.a
__pycache__
*.snfoundry

# aws_lambda
aws_lambda/fee_balancer/data
aws_lambda/fee_balancer/build
aws_lambda/fee_balancer/deployments
aws_lambda/fee_balancer/constants.py
aws_lambda/fee_balancer/starknet.py

aws_lambda/relayers_observer/data
aws_lambda/relayers_observer/build
aws_lambda/relayers_observer/deployments
aws_lambda/relayers_observer/constants.py
aws_lambda/relayers_observer/starknet.py

cdk.out
29 changes: 29 additions & 0 deletions aws_lambda/fee_balancer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# trunk-ignore-all(checkov/CKV_DOCKER_2)
# trunk-ignore-all(checkov/CKV_DOCKER_3)
# trunk-ignore-all(hadolint/DL3013)
# trunk-ignore-all(hadolint/DL3033)
FROM amazon/aws-lambda-python:3.10

RUN yum update -y && \
yum install -y gcc gmp-devel && \
yum clean all && \
rm -rf /var/cache/yum

WORKDIR /var/task

RUN pip install --no-cache-dir uv && uv venv

COPY pyproject.toml ./

RUN pip install --no-cache-dir -e '.[lambda-dependencies]'

COPY build ./build
COPY deployments ./deployments
COPY relayers.json ./relayers.json
COPY .env ./
COPY fee_balancer.py ./
COPY constants.py ./kakarot_scripts/constants.py
COPY starknet.py ./kakarot_scripts/utils/starknet.py
COPY data ./kakarot_scripts/data

CMD ["fee_balancer.lambda_handler"]
9 changes: 9 additions & 0 deletions aws_lambda/fee_balancer/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env python3

import aws_cdk as cdk
from fee_balancer_lambda_stack import FeeBalancerLambdaStack

app = cdk.App()
FeeBalancerLambdaStack(app, "FeeBalancerLambdaStack")

app.synth()
71 changes: 71 additions & 0 deletions aws_lambda/fee_balancer/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"app": "python3 app.py",
"watch": {
"include": ["**"],
"exclude": [
"README.md",
"cdk*.json",
"requirements*.txt",
"source.bat",
"**/__init__.py",
"**/__pycache__",
"tests"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true
}
}
142 changes: 142 additions & 0 deletions aws_lambda/fee_balancer/fee_balancer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import asyncio
import json
import logging
import os

import boto3
from web3 import Web3

from kakarot_scripts.utils.starknet import (
get_balance,
get_contract,
get_eth_contract,
get_starknet_account,
wait_for_transaction,
)

logger = logging.getLogger()
logger.setLevel("INFO")

client = boto3.client("secretsmanager")
ssm = boto3.client("ssm")
node_url = os.getenv("NODE_URL")
web3 = Web3(Web3.HTTPProvider(node_url))

with open("coinbase_abi.json", "r") as f:
coinbase_abi = json.load(f)

contract_address = os.getenv("COINBASE_CONTRACT_ADDRESS")

# Create smart contract instance
contract = web3.eth.contract(address=contract_address, abi=coinbase_abi)


def lambda_handler(event, context):
return asyncio.get_event_loop().run_until_complete(check_and_fund_relayers())


async def check_and_fund_relayers():
# Load relayers information from JSON file
with open("relayers.json", "r") as f:
relayers = json.load(f)

# Retrieve starknet secret from AWS Secrets Manager
response = client.get_secret_value(SecretId="relayers_fund_account")
secret_dict = json.loads(response["SecretString"])

starknet_address, starknet_private_key = next(iter(secret_dict.items()))
starknet_account = await get_starknet_account(
starknet_address, starknet_private_key
)
eth_contract = await get_eth_contract(starknet_account)

# Retrieve eth secret from AWS Secrets Manager
response = client.get_secret_value(SecretId="eth_coinbase_owner")
secret_dict = json.loads(response["SecretString"])

eth_address, eth_private_key = next(iter(secret_dict.items()))
nonce = web3.eth.get_transaction_count(eth_address)

account_balance_before_withdraw = await get_balance(
starknet_account.starknet_address, eth_contract
)

# withdraw fees from coinbase contract
await withdraw_fee(starknet_address, nonce, eth_address, eth_private_key)

account_balance = await get_balance(starknet_account.starknet_address, eth_contract)
relayers_total_balance = await get_total_balance_of_relayers(relayers, eth_contract)

actual_fee = account_balance - account_balance_before_withdraw

# get the prev balances
relayers_prev_total_balance = int(os.getenv("PREV_TOTAL_BALANCE"))

# get the earning percentage
earning_percentage = int(os.getenv("EARNING_PERCENTAGE"))

cairo_counter = get_contract("Kakarot")

yield cairo_counter

base_fee = await cairo_counter.functions["get_base_fee"].call()
logger.info(f"Base fee: {base_fee}")
changed_fee = base_fee

# check if the relayers balance is less than the prev balance
if relayers_prev_total_balance + actual_fee < relayers_total_balance:
# increase the base fee of 12.5%
changed_fee += base_fee * 0.125
# check if the relayers balance is more than the prev balance + the acceptable earning percentage
elif relayers_prev_total_balance + actual_fee > relayers_total_balance + (
earning_percentage * actual_fee / 100
):
# decrease the base fee of 12.5%
changed_fee -= base_fee * 0.125
else:
logger.info("No changes to the base fee")

if changed_fee != base_fee:
tx = await cairo_counter.functions["set_base_fee"].invoke_v1(base_fee)
await wait_for_transaction(tx.hash)

return {
"statusCode": 200,
}


async def get_total_balance_of_relayers(relayers, eth_contract, block_number):
total_balance = 0

for relayer in relayers:
account_balance = await get_balance(
relayer["address"], eth_contract, block_number
)
total_balance += account_balance

return total_balance


async def withdraw_fee(starknet_address, nonce, eth_address, eth_private_key):
Chain_id = web3.eth.chain_id

# Call your function
call_function = contract.functions.withdraw(
toStarknetAddress=starknet_address
).build_transaction({"chainId": Chain_id, "from": eth_address, "nonce": nonce})

# Sign transaction
signed_tx = web3.eth.account.sign_transaction(
call_function, private_key=eth_private_key
)

# Send transaction
send_tx = web3.eth.send_raw_transaction(signed_tx.raw_transaction)

# Wait for transaction receipt
tx_receipt = web3.eth.wait_for_transaction_receipt(send_tx)
logger.info(tx_receipt)


if __name__ == "__main__":
asyncio.run(check_and_fund_relayers())
108 changes: 108 additions & 0 deletions aws_lambda/fee_balancer/fee_balancer_lambda_stack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import os
import shutil

from aws_cdk import Duration, Stack
from aws_cdk import aws_events as events
from aws_cdk import aws_events_targets as targets
from aws_cdk import aws_iam as iam
from aws_cdk import aws_lambda as _lambda
from constructs import Construct


class FeeBalancerLambdaStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)

self.build_lambda_func()

def build_lambda_func(self):
node_url = os.environ.get("NODE_URL")
if node_url is None:
raise ValueError("NODE_URL environment variable is not set")

coinbase_contract_address = os.environ.get("COINBASE_CONTRACT_ADDRESS")
if coinbase_contract_address is None:
raise ValueError(
"COINBASE_CONTRACT_ADDRESS environment variable is not set"
)

prev_total_balance = os.environ.get("PREV_TOTAL_BALANCE")
if prev_total_balance is None:
raise ValueError("PREV_TOTAL_BALANCE environment variable is not set")

earning_percentage = os.environ.get("EARNING_PERCENTAGE")
if earning_percentage is None:
raise ValueError("EARNING_PERCENTAGE environment variable is not set")

dest_dir = "./"
shutil.copy(
"../../kakarot_scripts/constants.py", os.path.join(dest_dir, "constants.py")
)
shutil.copy(
"../../kakarot_scripts/utils/starknet.py",
os.path.join(dest_dir, "starknet.py"),
)
shutil.copy("../../.env", os.path.join(dest_dir, ".env"))
shutil.copytree(
"../../build",
os.path.join(dest_dir, "build"),
dirs_exist_ok=True,
)
shutil.copytree(
"../../deployments",
os.path.join(dest_dir, "deployments"),
dirs_exist_ok=True,
)
shutil.copytree(
"../../kakarot_scripts/data",
os.path.join(dest_dir, "data"),
dirs_exist_ok=True,
)
self.prediction_lambda = _lambda.DockerImageFunction(
scope=self,
id="fee_balancer_lambda",
function_name="fee_balancer",
code=_lambda.DockerImageCode.from_image_asset(directory="."),
environment={
"NODE_URL": node_url,
"COINBASE_CONTRACT_ADDRESS": coinbase_contract_address,
"PREV_TOTAL_BALANCE": prev_total_balance,
"EARNING_PERCENTAGE": earning_percentage,
},
environment_encryption=None,
timeout=Duration.minutes(1),
)

secret_arns = [
os.environ.get("RELAYERS_FUND_ACCOUNT_SECRET_ARN"),
os.environ.get("ETH_COINBASE_OWNER_SECRET_ARN"),
]
secret_arns = [arn for arn in secret_arns if arn is not None]

self.prediction_lambda.add_to_role_policy(
iam.PolicyStatement(
actions=["secretsmanager:GetSecretValue"],
resources=secret_arns,
)
)

# param_arns = [
# os.environ.get("RELAYERS_PREV_TOTAL_BALANCE_PARAM_ARN"),
# os.environ.get("ACCOUNT_PREV_BALANCE_PARAM_ARN"),
# os.environ.get("EARNING_PERCENTAGE_PARAM_ARN"),
# ]
# param_arns = [arn for arn in param_arns if arn is not None]

# self.prediction_lambda.add_to_role_policy(
# iam.PolicyStatement(
# actions=["ssm:GetParameter", "ssm:PutParameter"],
# resources=param_arns,
# )
# )

events.Rule(
self,
"ScheduleRule",
schedule=events.Schedule.cron(minute="0/20"),
targets=[targets.LambdaFunction(self.prediction_lambda)],
)
Loading

0 comments on commit 87e0a82

Please sign in to comment.