def main():
global spell_contract
global sspell_contract
global traderjoe_router_contract
global spell
global sspell
try:
network.connect("avax-main")
# Avalanche supports EIP-1559 transactions, so we set the priority fee
# and allow the base fee to change as needed
network.priority_fee('5 gwei')
# Can set a limit on maximum fee, if desired
#network.max_fee('200 gwei')
except:
sys.exit(
"Could not connect to Avalanche! Verify that brownie lists the Avalanche Mainnet using 'brownie networks list'"
)
try:
global user
user = accounts.load("test_account")
except:
sys.exit(
"Could not load account! Verify that your account is listed using 'brownie accounts list' and that you are using the correct password. If you have not added an account, run 'brownie accounts' now."
)
[... continued]
print("\nContracts loaded:")
spell_contract = contract_load(SPELL_CONTRACT_ADDRESS, "Avalanche Token: SPELL")
sspell_contract = contract_load(SSPELL_CONTRACT_ADDRESS, "Avalanche Token: sSPELL")
router_contract = contract_load(
TRADERJOE_ROUTER_CONTRACT_ADDRESS, "TraderJoe: Router"
)
spell = {
"address": SPELL_CONTRACT_ADDRESS,
"contract": spell_contract,
"name": None,
"symbol": None,
"balance": None,
"decimals": None,
}
sspell = {
"address": SSPELL_CONTRACT_ADDRESS,
"contract": sspell_contract,
"name": None,
"symbol": None,
"balance": None,
"decimals": None,
}
spell["symbol"] = get_token_symbol(spell["contract"])
spell["name"] = get_token_name(spell["contract"])
spell["balance"] = get_token_balance(spell_contract, user)
spell["decimals"] = get_token_decimals(spell_contract)
sspell["symbol"] = get_token_symbol(sspell["contract"])
sspell["name"] = get_token_name(sspell["contract"])
sspell["balance"] = get_token_balance(sspell_contract, user)
sspell["decimals"] = get_token_decimals(sspell_contract)
if (spell["balance"] == 0) and (sspell["balance"] == 0):
sys.exit("No tokens found!")
[... continued]
지난 번에 만들었던 helper function을 활용했다.
# Confirm approvals for tokens
print("\nChecking Approvals:")
if get_approval(spell["contract"], router_contract, user):
print(f"• {spell['symbol']} OK")
else:
token_approve(spell["contract"], router_contract)
if get_approval(sspell["contract"], router_contract, user):
print(f"• {sspell['symbol']} OK")
else:
token_approve(sspell["contract"], router_contract)
try:
with open(STAKING_RATE_FILENAME, "r") as file:
base_staking_rate = float(file.read().strip())
print(f"\nEthereum L1 Staking Rate: {base_staking_rate}")
except FileNotFoundError:
sys.exit(
"Cannot load the base Abracadabra SPELL/sSPELL staking rate. Run `python3 abra_rate.py` and try again."
)
balance_refresh = True
[... continued]
while True:
loop_start = time.time()
try:
with open(STAKING_RATE_FILENAME, "r") as file:
if (result := float(file.read().strip())) != base_staking_rate:
base_staking_rate = result
print(f"Updated staking rate: {base_staking_rate}")
except FileNotFoundError:
sys.exit(
"Cannot load the base Abracadabra SPELL/sSPELL staking rate. Run `python3 abra_rate.py` and try again."
)
if balance_refresh:
time.sleep(10)
spell["balance"] = get_token_balance(spell_contract, user)
sspell["balance"] = get_token_balance(sspell_contract, user)
print("\nAccount Balance:")
print(
f"• Token #1: {int(spell['balance']/(10**spell['decimals']))} {spell['symbol']} ({spell['name']})"
)
print(
f"• Token #2: {int(sspell['balance']/(10**sspell['decimals']))} {sspell['symbol']} ({sspell['name']})"
)
print()
balance_refresh = False
last_ratio_spell_to_sspell = 0
last_ratio_sspell_to_spell = 0
[... continued]
# get quotes and execute SPELL -> sSPELL swaps only if we have a balance of SPELL
if spell["balance"]:
#get_swap_rate을 실행해서 result에 할당. 성공적으로 실행했다면 if문을 통과할 것이다.
if result := get_swap_rate(
token_in_quantity=spell["balance"],
token_in_address=spell["address"],
token_out_address=sspell["address"],
router=router_contract,
):
spell_in, sspell_out = result
ratio_spell_to_sspell = round(sspell_out / spell_in, 4)
# print and save any updated swap values since last loop
if ratio_spell_to_sspell != last_ratio_spell_to_sspell:
print(
f"{datetime.datetime.now().strftime('[%I:%M:%S %p]')} {spell['symbol']} → {sspell['symbol']}: ({ratio_spell_to_sspell:.4f}/{1 / (base_staking_rate * (1 + THRESHOLD_SPELL_TO_SSPELL)):.4f})"
)
last_ratio_spell_to_sspell = ratio_spell_to_sspell
else:
# abandon the for loop to avoid re-using stale data
break
# execute SPELL -> sSPELL arb if trigger is satisfied
if ratio_spell_to_sspell >= 1 / (
base_staking_rate * (1 + THRESHOLD_SPELL_TO_SSPELL)
):
print(
f"*** EXECUTING SWAP OF {int(spell_in / (10**spell['decimals']))} {spell['symbol']} AT BLOCK {chain.height} ***"
)
if token_swap(
token_in_quantity=spell_in,
token_in_address=spell["address"],
token_out_quantity=sspell_out,
token_out_address=sspell["address"],
router=router_contract,
):
balance_refresh = True
if ONE_SHOT:
sys.exit("single shot complete!")
# get quotes and execute sSPELL -> SPELL swaps only if we have a balance of sSPELL
if sspell["balance"]:
if result := get_swap_rate(
token_in_quantity=sspell["balance"],
token_in_address=sspell["address"],
token_out_address=spell["address"],
router=router_contract,
):
sspell_in, spell_out = result
ratio_sspell_to_spell = round(spell_out / sspell_in, 4)
# print and save any updated swap values since last loop
if ratio_sspell_to_spell != last_ratio_sspell_to_spell:
print(
f"{datetime.datetime.now().strftime('[%I:%M:%S %p]')} {sspell['symbol']} → {spell['symbol']}: ({ratio_sspell_to_spell:.4f}/{base_staking_rate * (1 + THRESHOLD_SSPELL_TO_SPELL):.4f})"
)
last_ratio_sspell_to_spell = ratio_sspell_to_spell
else:
# abandon the for loop to avoid re-using stale data
break
# execute sSPELL -> SPELL arb if trigger is satisfied
if ratio_sspell_to_spell >= base_staking_rate * (
1 + THRESHOLD_SSPELL_TO_SPELL
):
print(
f"*** EXECUTING SWAP OF {int(sspell_in/(10**sspell['decimals']))} {sspell['symbol']} AT BLOCK {chain.height} ***"
)
if token_swap(
token_in_quantity=sspell_in,
token_in_address=sspell["address"],
token_out_quantity=spell_out,
token_out_address=spell["address"],
router=router_contract,
):
balance_refresh = True
if ONE_SHOT:
sys.exit("single shot complete!")
loop_end = time.time()
# Control the loop timing more precisely by measuring start and end time and sleeping as needed
if (loop_end - loop_start) >= LOOP_TIME:
continue
else:
time.sleep(LOOP_TIME - (loop_end - loop_start))
continue
[... continued]
위 예제에서 바다코끼리 연산자(:=
)가 사용됐다. 함수 리턴값을 변수에 할당해서 재사용할 수 있다!
sSPELL과 SPELL 토큰을 교환할 때 계산식을 살펴볼 필요가 있다.
# execute sSPELL -> SPELL arb if trigger is satisfied
if ratio_sspell_to_spell >= base_staking_rate * (
1 + THRESHOLD_SSPELL_TO_SPELL
):
# execute SPELL -> sSPELL arb if trigger is satisfied
if ratio_spell_to_sspell >= 1 / (
base_staking_rate * (1 + THRESHOLD_SPELL_TO_SSPELL)
):
base_staking_rate
은 고정값이라고 생각하고 정해놓은 Threshold 퍼센트 이상으로 비율이 올라가거나 내려갈 때 스왑을 진행한다. 예를 들어서 base_staking_rate
이 1.1이라고 하자. 1sSPELL = 1.1SPELL
그럼 sSPELL토큰을 언제 SPELL토큰으로 바꾸는 것이 이득일까? 당연히 1.1 이상일 때 바꾸는 것이 이득이다. 극단적으로 생각했을 때 1sSPELL = 2SPELL
이면 돈복사가 가능하다. 그렇다면 반대로 SPELL 토큰을 언제 sSPELL 토큰으로 바꾸는 것이 이득일까? 1.1 이하일 때 바꾸는 것이 이득이다. 다시 한 번 극단적으로 생각했을 때 1sSPELL = 0.5SPELL
이면 돈복사가 가능하다.
실제 시나리오를 생각해보자. 우리는 SPELL 토큰 갯수를 늘리는 것이 목표다. 처음 11개의 SPELL 토큰을 갖고 있다고 가정했을 때 평소에는 10개의 sSPELL로 교환할 수 있다. 극한의 효율을 원하는 우리는 1sSPELL = 0.5SPELL
일 때 교환할 것이다. 우리의 바람대로 비율이 낮아졌을 때 스왑을 진행하면 22개의 sSPELL을 얻을 수 있다. 시간이 지나고 갑자기 1sSPELL = 2SPELL
비율이 됐을 때 다시 교환한다면 44개의 SPELL 토큰을 얻을 수 있다. 교환비율이 달라졌을 뿐인데 우리는 두 번의 스왑으로 300% 수익을 얻었다. 다소 극단적인 상황을 예시로 들었지만 이렇게 하는게 훨씬 이해하기 쉽다. 따라서 결론은 base_staking_rate
을 두고 일정 비율을 위 아래로 둬서 그 이상으로 올라가거나 그 아래로 내려갈 때 서로 다른 방향으로 스왑을 진행한다는 것이다.