Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fee-balancer lambda function #1613

Merged
merged 3 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
31 changes: 31 additions & 0 deletions aws_lambda/fee_balancer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# trunk-ignore-all(checkov/CKV_DOCKER_2)
# trunk-ignore-all(checkov/CKV_DOCKER_3)
# trunk-ignore-all(hadolint/DL3013)
# trunk-ignore-all(hadolint/DL3033)
# trunk-ignore-all(trivy/DS002)
# trunk-ignore-all(trivy/DS026)
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
}
}
139 changes: 139 additions & 0 deletions aws_lambda/fee_balancer/fee_balancer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# trunk-ignore-all(ruff/ARG001)
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)
94 changes: 94 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,94 @@
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,
)
)

events.Rule(
self,
"ScheduleRule",
schedule=events.Schedule.cron(minute="0/20"),
targets=[targets.LambdaFunction(self.prediction_lambda)],
)
26 changes: 26 additions & 0 deletions aws_lambda/fee_balancer/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[project]
name = "fee_balancer"
version = "0.1.0"
description = "Fee Balancer Lambda function"
requires-python = ">=3.10,<3.11"

[project.optional-dependencies]
lambda-dependencies = [
"starknet-py==0.23.0",
"python-dotenv==0.21.0",
"web3==6",
"async_lru==2.0.4",
"cairo-lang==0.13.1",
"requests==2.32.3",
"eth_keys==0.5.1",
"boto3==1.35.36",
]

cdk-dependencies = ["aws-cdk-lib==2.161.1", "constructs>=10.0.0,<11.0.0"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build]
include = ["fee_balancer.py", "fee_balancer_lambda_stack.py", "app.py"]
Loading
Loading