mirror of
https://github.com/logos-messaging/logos-messaging-nim.git
synced 2026-01-05 23:43:07 +00:00
first implementation of token minting and approval
This commit is contained in:
parent
cba55f6fb5
commit
2aa0a64349
@ -81,14 +81,157 @@ proc getForgePath*(): string =
|
|||||||
forgePath = joinPath(forgePath, ".foundry/bin/forge")
|
forgePath = joinPath(forgePath, ".foundry/bin/forge")
|
||||||
return $forgePath
|
return $forgePath
|
||||||
|
|
||||||
proc deployTestToken*(): Future[string] {.async.} =
|
contract(ERC20Token):
|
||||||
|
proc approve(spender: Address, amount: UInt256)
|
||||||
|
proc allowance(owner: Address, spender: Address): UInt256 {.view.}
|
||||||
|
proc balanceOf(account: Address): UInt256 {.view.}
|
||||||
|
proc mint(recipient: Address, amount: UInt256)
|
||||||
|
|
||||||
|
proc getTokenBalance*(
|
||||||
|
web3: Web3, tokenAddress: Address, account: Address
|
||||||
|
): Future[UInt256] {.async.} =
|
||||||
|
let token = web3.contractSender(ERC20Token, tokenAddress)
|
||||||
|
return await token.balanceOf(account).call()
|
||||||
|
|
||||||
|
proc ethToWei(eth: UInt256): UInt256 =
|
||||||
|
eth * 1000000000000000000.u256
|
||||||
|
|
||||||
|
proc sendMintCall*(
|
||||||
|
web3: Web3,
|
||||||
|
accountFrom: Address,
|
||||||
|
tokenAddress: Address,
|
||||||
|
recipientAddress: Address,
|
||||||
|
amountTokens: UInt256,
|
||||||
|
recipientBalanceBeforeExpectedTokens: Option[UInt256] = none(UInt256),
|
||||||
|
): Future[TxHash] {.async.} =
|
||||||
|
# Create mint transaction
|
||||||
|
let mintSelector = "0x40c10f19" # Pad the address and amount to 32 bytes each
|
||||||
|
let addressHex = recipientAddress.toHex()
|
||||||
|
let paddedAddress = addressHex.align(64, '0')
|
||||||
|
|
||||||
|
let amountHex = amountTokens.toHex()
|
||||||
|
let amountWithout0x =
|
||||||
|
if amountHex.startsWith("0x"):
|
||||||
|
amountHex[2 .. ^1]
|
||||||
|
else:
|
||||||
|
amountHex
|
||||||
|
let paddedAmount = amountWithout0x.align(64, '0') # Create the call data
|
||||||
|
let mintCallData = mintSelector & paddedAddress & paddedAmount # Get gas price
|
||||||
|
let gasPrice = int(await web3.provider.eth_gasPrice())
|
||||||
|
|
||||||
|
# Create the transaction
|
||||||
|
var tx: TransactionArgs
|
||||||
|
tx.`from` = Opt.some(accountFrom)
|
||||||
|
tx.to = Opt.some(tokenAddress)
|
||||||
|
tx.value = Opt.some(0.u256) # No ETH is sent for token operations
|
||||||
|
tx.gasPrice = Opt.some(Quantity(gasPrice))
|
||||||
|
tx.data = Opt.some(byteutils.hexToSeqByte(mintCallData))
|
||||||
|
|
||||||
|
debug "Sending mint call"
|
||||||
|
# Send the transaction
|
||||||
|
let txHash = await web3.send(tx)
|
||||||
|
|
||||||
|
let balanceOfSelector = "0x70a08231"
|
||||||
|
let balanceCallData = balanceOfSelector & paddedAddress
|
||||||
|
|
||||||
|
# Wait a bit for transaction to be mined
|
||||||
|
await sleepAsync(500.milliseconds)
|
||||||
|
|
||||||
|
var balanceCallTx: TransactionArgs
|
||||||
|
balanceCallTx.to = Opt.some(tokenAddress)
|
||||||
|
balanceCallTx.data = Opt.some(byteutils.hexToSeqByte(balanceCallData))
|
||||||
|
|
||||||
|
# Make the call to get updated balance
|
||||||
|
let balanceResponse = await web3.provider.eth_call(balanceCallTx, "latest")
|
||||||
|
|
||||||
|
let balanceHexStr = "0x" & byteutils.toHex(balanceResponse)
|
||||||
|
let balanceAfterTokens = stint.parse(balanceHexStr, UInt256, 16)
|
||||||
|
let balanceAfterExpectedTokens =
|
||||||
|
recipientBalanceBeforeExpectedTokens.get() + amountTokens
|
||||||
|
debug "token balance after minting", balanceAfterTokens = balanceAfterTokens
|
||||||
|
assert balanceAfterTokens == balanceAfterExpectedTokens,
|
||||||
|
fmt"Token balance is {balanceAfterTokens} but expected {balanceAfterExpectedTokens}"
|
||||||
|
|
||||||
|
let balance3 = await web3.provider.eth_getBalance(recipientAddress, "latest")
|
||||||
|
debug "sendMintCall: after mint recipientAddress account balance: ",
|
||||||
|
recipientAddress = recipientAddress, balance = balance3
|
||||||
|
|
||||||
|
return txHash
|
||||||
|
|
||||||
|
proc checkApproval*(
|
||||||
|
web3: Web3, tokenAddress: Address, owner: Address, spender: Address
|
||||||
|
): Future[UInt256] {.async.} =
|
||||||
|
let token = web3.contractSender(ERC20Token, tokenAddress)
|
||||||
|
let allowance = await token.allowance(owner, spender).call()
|
||||||
|
debug "Current allowance", owner = owner, spender = spender, allowance = allowance
|
||||||
|
return allowance
|
||||||
|
|
||||||
|
proc sendApproveCall*(
|
||||||
|
web3: Web3,
|
||||||
|
accountFrom: Address,
|
||||||
|
privateKey: keys.PrivateKey,
|
||||||
|
tokenAddress: Address,
|
||||||
|
spender: Address,
|
||||||
|
amountWei: UInt256,
|
||||||
|
): Future[TxHash] {.async.} =
|
||||||
|
# # ERC20 approve function signature: approve(address spender, uint256 amount)
|
||||||
|
# # Create the contract call data
|
||||||
|
# # Method ID for approve(address,uint256) is 0x095ea7b3
|
||||||
|
|
||||||
|
# Temporarily set the private key
|
||||||
|
let oldPrivateKey = web3.privateKey
|
||||||
|
web3.privateKey = Opt.some(privateKey)
|
||||||
|
web3.lastKnownNonce = Opt.none(Quantity) # Reset nonce tracking
|
||||||
|
|
||||||
|
# Create approve transaction
|
||||||
|
let approveSelector = "0x095ea7b3"
|
||||||
|
|
||||||
|
let addressHex = spender.toHex() # Already without 0x
|
||||||
|
let paddedAddress = addressHex.align(64, '0')
|
||||||
|
let amountHex = amountWei.toHex()
|
||||||
|
# let amountWithout0x =
|
||||||
|
# if amountHex.startsWith("0x"):
|
||||||
|
# amountHex[2 .. ^1]
|
||||||
|
# else:
|
||||||
|
# amountHex
|
||||||
|
let paddedAmount = amountHex.align(64, '0')
|
||||||
|
let approveCallData = approveSelector & paddedAddress & paddedAmount
|
||||||
|
|
||||||
|
# Get gas price
|
||||||
|
let gasPrice = int(await web3.provider.eth_gasPrice())
|
||||||
|
|
||||||
|
# Create the transaction
|
||||||
|
var tx: TransactionArgs
|
||||||
|
tx.`from` = Opt.some(accountFrom)
|
||||||
|
tx.to = Opt.some(tokenAddress)
|
||||||
|
tx.value = Opt.some(0.u256)
|
||||||
|
tx.gasPrice = Opt.some(Quantity(gasPrice))
|
||||||
|
tx.gas = Opt.some(Quantity(100000)) # Add gas limit
|
||||||
|
tx.data = Opt.some(byteutils.hexToSeqByte(approveCallData))
|
||||||
|
tx.chainId = Opt.some(Quantity(CHAIN_ID))
|
||||||
|
|
||||||
|
debug "Sending approve call with transaction", tx = tx
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Send will automatically sign because privateKey is set
|
||||||
|
let txHash = await web3.send(tx)
|
||||||
|
return txHash
|
||||||
|
except CatchableError as e:
|
||||||
|
error "Failed to send approve transaction", error = e.msg
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
# Always restore the old private key
|
||||||
|
web3.privateKey = oldPrivateKey
|
||||||
|
|
||||||
|
proc deployTestToken*(
|
||||||
|
pk: keys.PrivateKey, acc: Address, web3: Web3
|
||||||
|
): Future[Address] {.async.} =
|
||||||
## Executes a Foundry forge script that deploys the a token contract (ERC-20) used for testing. This is a prerequisite to enable the contract deployment and this token contract address needs to be minted and approved for the accounts that need to register membership with the contract
|
## Executes a Foundry forge script that deploys the a token contract (ERC-20) used for testing. This is a prerequisite to enable the contract deployment and this token contract address needs to be minted and approved for the accounts that need to register membership with the contract
|
||||||
## submodulePath: path to the submodule containing contract deploy scripts
|
## submodulePath: path to the submodule containing contract deploy scripts
|
||||||
|
|
||||||
# All RLN related tests should be run from the root directory of the project
|
# All RLN related tests should be run from the root directory of the project
|
||||||
let submodulePath = "./vendor/waku-rlnv2-contract"
|
let submodulePath = "./vendor/waku-rlnv2-contract"
|
||||||
|
|
||||||
let PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
|
|
||||||
let forgePath = getForgePath()
|
let forgePath = getForgePath()
|
||||||
debug "Forge path", forgePath
|
debug "Forge path", forgePath
|
||||||
|
|
||||||
@ -124,7 +267,7 @@ proc deployTestToken*(): Future[string] {.async.} =
|
|||||||
|
|
||||||
# Deploy TestToken contract
|
# Deploy TestToken contract
|
||||||
let forgeCmdTestToken =
|
let forgeCmdTestToken =
|
||||||
fmt"""cd {submodulePath} && {forgePath} script test/TestToken.sol --broadcast -vv --rpc-url http://localhost:8540 --tc TestTokenFactory --private-key {PRIVATE_KEY} && rm -rf broadcast/*/*/run-1*.json && rm -rf cache/*/*/run-1*.json"""
|
fmt"""cd {submodulePath} && {forgePath} script test/TestToken.sol --broadcast -vv --rpc-url http://localhost:8540 --tc TestTokenFactory --private-key {pk} && rm -rf broadcast/*/*/run-1*.json && rm -rf cache/*/*/run-1*.json"""
|
||||||
let (outputDeployTestToken, exitCodeDeployTestToken) = execCmdEx(forgeCmdTestToken)
|
let (outputDeployTestToken, exitCodeDeployTestToken) = execCmdEx(forgeCmdTestToken)
|
||||||
trace "Executed forge command to deploy TestToken contract",
|
trace "Executed forge command to deploy TestToken contract",
|
||||||
output = outputDeployTestToken
|
output = outputDeployTestToken
|
||||||
@ -144,9 +287,51 @@ proc deployTestToken*(): Future[string] {.async.} =
|
|||||||
##TODO: raise exception here?
|
##TODO: raise exception here?
|
||||||
let testTokenAddress = testTokenAddressRes.get()
|
let testTokenAddress = testTokenAddressRes.get()
|
||||||
debug "Address of the TestToken contract", testTokenAddress
|
debug "Address of the TestToken contract", testTokenAddress
|
||||||
putEnv("TOKEN_ADDRESS", testTokenAddress)
|
debug "TestToken contract deployer account details", account = acc, pk = pk
|
||||||
|
|
||||||
return testTokenAddress
|
let testTokenAddressBytes = hexToByteArray[20](testTokenAddress)
|
||||||
|
let testTokenAddressAddress = Address(testTokenAddressBytes)
|
||||||
|
|
||||||
|
return testTokenAddressAddress
|
||||||
|
|
||||||
|
proc approveAndVerify*(
|
||||||
|
web3: Web3,
|
||||||
|
accountFrom: Address,
|
||||||
|
privateKey: keys.PrivateKey,
|
||||||
|
tokenAddress: Address,
|
||||||
|
spender: Address,
|
||||||
|
amountWei: UInt256,
|
||||||
|
): Future[bool] {.async.} =
|
||||||
|
debug "Starting approval process",
|
||||||
|
owner = accountFrom,
|
||||||
|
tokenAddress = tokenAddress,
|
||||||
|
spender = spender,
|
||||||
|
amount = amountWei
|
||||||
|
|
||||||
|
# Send approval
|
||||||
|
let txHash = await sendApproveCall(
|
||||||
|
web3, accountFrom, privateKey, tokenAddress, spender, amountWei
|
||||||
|
)
|
||||||
|
|
||||||
|
debug "Approval transaction sent", txHash = txHash
|
||||||
|
|
||||||
|
# Wait for transaction to be mined
|
||||||
|
let receipt = await web3.getMinedTransactionReceipt(txHash)
|
||||||
|
debug "Transaction mined", status = receipt.status, blockNumber = receipt.blockNumber
|
||||||
|
|
||||||
|
# Check if status is present and successful
|
||||||
|
if receipt.status.isNone or receipt.status.get != 1.Quantity:
|
||||||
|
error "Approval transaction failed"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Give it a moment for the state to settle
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
|
||||||
|
# Check allowance after mining
|
||||||
|
let allowanceAfter = await checkApproval(web3, tokenAddress, accountFrom, spender)
|
||||||
|
debug "Allowance after approval", amount = allowanceAfter
|
||||||
|
|
||||||
|
return allowanceAfter >= amountWei
|
||||||
|
|
||||||
proc executeForgeContractDeployScripts*(): Future[Address] {.async.} =
|
proc executeForgeContractDeployScripts*(): Future[Address] {.async.} =
|
||||||
## Executes a set of foundry forge scripts required to deploy the RLN contract and returns the deployed proxy contract address
|
## Executes a set of foundry forge scripts required to deploy the RLN contract and returns the deployed proxy contract address
|
||||||
@ -223,10 +408,10 @@ proc executeForgeContractDeployScripts*(): Future[Address] {.async.} =
|
|||||||
let wakuRlnV2AddressRes =
|
let wakuRlnV2AddressRes =
|
||||||
getContractAddressFromDeployScriptOutput(outputDeployWakuRln)
|
getContractAddressFromDeployScriptOutput(outputDeployWakuRln)
|
||||||
if wakuRlnV2AddressRes.isErr():
|
if wakuRlnV2AddressRes.isErr():
|
||||||
error "Failed to get LinearPriceCalculator contract address from deploy script output"
|
error "Failed to get WakuRlnV2 contract address from deploy script output"
|
||||||
##TODO: raise exception here?
|
##TODO: raise exception here?
|
||||||
let wakuRlnV2Address = wakuRlnV2AddressRes.get()
|
let wakuRlnV2Address = wakuRlnV2AddressRes.get()
|
||||||
debug "Address of the LinearPriceCalculator contract", wakuRlnV2Address
|
debug "Address of the WakuRlnV2 contract", wakuRlnV2Address
|
||||||
putEnv("WAKURLNV2_ADDRESS", wakuRlnV2Address)
|
putEnv("WAKURLNV2_ADDRESS", wakuRlnV2Address)
|
||||||
|
|
||||||
# Deploy Proxy contract
|
# Deploy Proxy contract
|
||||||
@ -321,7 +506,7 @@ proc sendEthTransfer*(
|
|||||||
let balanceBeforeWei = await web3.provider.eth_getBalance(accountTo, "latest")
|
let balanceBeforeWei = await web3.provider.eth_getBalance(accountTo, "latest")
|
||||||
let balanceBeforeExpectedWei = accountToBalanceBeforeExpectedWei.get()
|
let balanceBeforeExpectedWei = accountToBalanceBeforeExpectedWei.get()
|
||||||
assert balanceBeforeWei == balanceBeforeExpectedWei,
|
assert balanceBeforeWei == balanceBeforeExpectedWei,
|
||||||
fmt"Balance is {balanceBeforeWei} but expected {balanceBeforeExpectedWei}"
|
fmt"Balance is {balanceBeforeWei} before transfer but expected {balanceBeforeExpectedWei}"
|
||||||
|
|
||||||
let gasPrice = int(await web3.provider.eth_gasPrice())
|
let gasPrice = int(await web3.provider.eth_gasPrice())
|
||||||
|
|
||||||
@ -334,17 +519,17 @@ proc sendEthTransfer*(
|
|||||||
# TODO: handle the error if sending fails
|
# TODO: handle the error if sending fails
|
||||||
let txHash = await web3.send(tx)
|
let txHash = await web3.send(tx)
|
||||||
|
|
||||||
|
# Wait a bit for transaction to be mined
|
||||||
|
await sleepAsync(2000.milliseconds)
|
||||||
|
|
||||||
if doBalanceAssert:
|
if doBalanceAssert:
|
||||||
let balanceAfterWei = await web3.provider.eth_getBalance(accountTo, "latest")
|
let balanceAfterWei = await web3.provider.eth_getBalance(accountTo, "latest")
|
||||||
let balanceAfterExpectedWei = accountToBalanceBeforeExpectedWei.get() + amountWei
|
let balanceAfterExpectedWei = accountToBalanceBeforeExpectedWei.get() + amountWei
|
||||||
assert balanceAfterWei == balanceAfterExpectedWei,
|
assert balanceAfterWei == balanceAfterExpectedWei,
|
||||||
fmt"Balance is {balanceAfterWei} but expected {balanceAfterExpectedWei}"
|
fmt"Balance is {balanceAfterWei} after transfer but expected {balanceAfterExpectedWei}"
|
||||||
|
|
||||||
return txHash
|
return txHash
|
||||||
|
|
||||||
proc ethToWei(eth: UInt256): UInt256 =
|
|
||||||
eth * 1000000000000000000.u256
|
|
||||||
|
|
||||||
proc createEthAccount*(
|
proc createEthAccount*(
|
||||||
ethAmount: UInt256 = 1000.u256
|
ethAmount: UInt256 = 1000.u256
|
||||||
): Future[(keys.PrivateKey, Address)] {.async.} =
|
): Future[(keys.PrivateKey, Address)] {.async.} =
|
||||||
@ -449,24 +634,41 @@ proc setupOnchainGroupManager*(
|
|||||||
|
|
||||||
let rlnInstance = rlnInstanceRes.get()
|
let rlnInstance = rlnInstanceRes.get()
|
||||||
|
|
||||||
let testTokenAddress = await deployTestToken()
|
|
||||||
putEnv("TOKEN_ADDRESS", testTokenAddress)
|
|
||||||
|
|
||||||
let contractAddress = await executeForgeContractDeployScripts()
|
|
||||||
# connect to the eth client
|
# connect to the eth client
|
||||||
let web3 = await newWeb3(ethClientUrl)
|
let web3 = await newWeb3(ethClientUrl)
|
||||||
|
|
||||||
let accounts = await web3.provider.eth_accounts()
|
let accounts = await web3.provider.eth_accounts()
|
||||||
web3.defaultAccount = accounts[0]
|
web3.defaultAccount = accounts[1]
|
||||||
|
|
||||||
let (privateKey, acc) = createEthAccount(web3)
|
let (privateKey, acc) = createEthAccount(web3)
|
||||||
|
|
||||||
# we just need to fund the default account
|
# we just need to fund the default account
|
||||||
# the send procedure returns a tx hash that we don't use, hence discard
|
# the send procedure returns a tx hash that we don't use, hence discard
|
||||||
discard await sendEthTransfer(
|
discard await sendEthTransfer(
|
||||||
web3, web3.defaultAccount, acc, ethToWei(1000.u256), some(0.u256)
|
web3, web3.defaultAccount, acc, ethToWei(5000.u256), some(0.u256)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let testTokenAddress = await deployTestToken(privateKey, acc, web3)
|
||||||
|
putEnv("TOKEN_ADDRESS", testTokenAddress.toHex())
|
||||||
|
|
||||||
|
discard await sendMintCall(
|
||||||
|
web3, web3.defaultAccount, testTokenAddress, acc, ethToWei(1000.u256), some(0.u256)
|
||||||
|
)
|
||||||
|
let contractAddress = await executeForgeContractDeployScripts()
|
||||||
|
|
||||||
|
let approvalSuccess = await approveAndVerify(
|
||||||
|
web3,
|
||||||
|
acc, # owner
|
||||||
|
privateKey,
|
||||||
|
testTokenAddress, # ERC20 token address
|
||||||
|
contractAddress, # spender - the proxy contract that will spend the tokens
|
||||||
|
ethToWei(500.u256),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Also check the token balance
|
||||||
|
let tokenBalance = await getTokenBalance(web3, testTokenAddress, acc)
|
||||||
|
debug "Token balance before register", owner = acc, balance = tokenBalance
|
||||||
|
|
||||||
let manager = OnchainGroupManager(
|
let manager = OnchainGroupManager(
|
||||||
ethClientUrls: @[ethClientUrl],
|
ethClientUrls: @[ethClientUrl],
|
||||||
ethContractAddress: $contractAddress,
|
ethContractAddress: $contractAddress,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user