Skip to content

Commit

Permalink
Merge branch 'feat/BCI-2538-update-examples' of github.com:smartcontr…
Browse files Browse the repository at this point in the history
…actkit/chainlink-starknet into feat/BCI-2538-update-examples
  • Loading branch information
chris-de-leon-cll committed Mar 5, 2024
2 parents 67c1962 + 1a4a106 commit 1015270
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 4 deletions.
1 change: 0 additions & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
},
"dependencies": {
"@chainlink/contracts": "^0.4.2",
"@chainlink/starknet": "^1.0.0",
"@openzeppelin/contracts": "^4.7.3",
"axios": "^0.24.0"
}
Expand Down
100 changes: 100 additions & 0 deletions contracts/test/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Account, RpcProvider, ec, uint256, constants } from 'starknet'

export const ERC20_ADDRESS = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'

export const DEVNET_URL = 'http://127.0.0.1:5050'
const DEVNET_NAME = 'devnet'
// This function loads options from the environment.
// It returns options for Devnet as default when nothing is configured in the environment.
export const makeFunderOptsFromEnv = () => {
const network = process.env.NETWORK || DEVNET_NAME
const gateway = process.env.NODE_URL || DEVNET_URL
const accountAddr = process.env.ACCOUNT?.toLowerCase()
const keyPair = ec.starkCurve.utils.randomPrivateKey()

return { network, gateway, accountAddr, keyPair }
}

interface FundAccounts {
account: string
amount: number
}

interface FunderOptions {
network?: string
gateway?: string
accountAddr?: string
keyPair?: Uint8Array
}

// Define the Strategy to use depending on the network.
export class Funder {
private opts: FunderOptions
private strategy: IFundingStrategy

constructor(opts: FunderOptions) {
this.opts = opts
if (this.opts.network === DEVNET_NAME) {
this.strategy = new DevnetFundingStrategy()
return
}
this.strategy = new AllowanceFundingStrategy()
}

// This function adds some funds to pre-deployed account that we are using in our test.
public async fund(accounts: FundAccounts[]) {
await this.strategy.fund(accounts, this.opts)
}
}

interface IFundingStrategy {
fund(accounts: FundAccounts[], opts: FunderOptions): Promise<void>
}

// Fund the Account on Devnet
class DevnetFundingStrategy implements IFundingStrategy {
public async fund(accounts: FundAccounts[], opts: FunderOptions) {
accounts.forEach(async (account) => {
const body = {
address: account.account,
amount: account.amount,
lite: true,
}
await fetch(`${opts.gateway}/mint`, {
method: 'post',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' },
})
})
}
}

// Fund the Account on Testnet
class AllowanceFundingStrategy implements IFundingStrategy {
public async fund(accounts: FundAccounts[], opts: FunderOptions) {
const provider = new RpcProvider({
nodeUrl: constants.NetworkName.SN_GOERLI,
})

const operator = new Account(provider, opts.accountAddr, opts.keyPair)

for (const account of accounts) {
const data = [
account.account,
uint256.bnToUint256(account.amount).low.toString(),
uint256.bnToUint256(account.amount).high.toString(),
]
const nonce = await operator.getNonce()
const hash = await operator.execute(
{
contractAddress: ERC20_ADDRESS,
entrypoint: 'transfer',
calldata: data,
},
undefined,
{ nonce },
)
await provider.waitForTransaction(hash.transaction_hash)
}
}
}
5 changes: 3 additions & 2 deletions contracts/test/emergency/StarknetValidator.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ethers, starknet, network } from 'hardhat'
import { BigNumber, Contract, ContractFactory } from 'ethers'
import { hash, number } from 'starknet'
import { hash } from 'starknet'
import {
Account,
StarknetContractFactory,
Expand All @@ -13,7 +13,8 @@ import { abi as aggregatorAbi } from '../../artifacts/@chainlink/contracts/src/v
import { abi as accessControllerAbi } from '../../artifacts/@chainlink/contracts/src/v0.8/interfaces/AccessControllerInterface.sol/AccessControllerInterface.json'
import { abi as starknetMessagingAbi } from '../../artifacts/vendor/starkware-libs/cairo-lang/src/starkware/starknet/solidity/IStarknetMessaging.sol/IStarknetMessaging.json'
import { deployMockContract, MockContract } from '@ethereum-waffle/mock-contract'
import { account, addCompilationToNetwork, expectSuccessOrDeclared } from '@chainlink/starknet'
import * as account from '../account'
import { addCompilationToNetwork, expectSuccessOrDeclared } from '../utils'

describe('StarknetValidator', () => {
/** Fake L2 target */
Expand Down
3 changes: 2 additions & 1 deletion contracts/test/ocr2/aggregator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { starknet } from 'hardhat'
import { ec, hash, num } from 'starknet'
import { Account, StarknetContract, StarknetContractFactory } from 'hardhat/types/runtime'
import { TIMEOUT } from '../constants'
import { account, expectInvokeError, expectSuccessOrDeclared } from '@chainlink/starknet'
import { expectInvokeError, expectSuccessOrDeclared } from '../utils'
import * as account from '../account'
import { bytesToFelts } from '@chainlink/starknet-gauntlet'

interface Oracle {
Expand Down
81 changes: 81 additions & 0 deletions contracts/test/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { expect } from 'chai'
import { artifacts, network } from 'hardhat'

// This function adds the build info to the test network so that the network knows
// how to handle custom errors. It is automatically done when testing
// against the default hardhat network.
export const addCompilationToNetwork = async (fullyQualifiedName: string) => {
if (network.name !== 'hardhat') {
// This is so that the network can know about custom errors.
// Running against the provided hardhat node does this automatically.

const buildInfo = await artifacts.getBuildInfo(fullyQualifiedName)
if (!buildInfo) {
throw Error('Cannot find build info')
}
const { solcVersion, input, output } = buildInfo
console.log('Sending compilation result for StarknetValidator test')
await network.provider.request({
method: 'hardhat_addCompilationResult',
params: [solcVersion, input, output],
})
console.log('Successfully sent compilation result for StarknetValidator test')
}
}

export const expectInvokeError = async (invoke: Promise<any>, expected?: string) => {
try {
await invoke
} catch (err: any) {
expectInvokeErrorMsg(err?.message, expected)
return // force
}
expect.fail("Unexpected! Invoke didn't error!?")
}

export const expectInvokeErrorMsg = (actual: string, expected?: string) => {
// Match transaction error
expect(actual).to.deep.contain('TRANSACTION_FAILED')
// Match specific error
if (expected) expectSpecificMsg(actual, expected)
}

export const expectCallError = async (call: Promise<any>, expected?: string) => {
try {
await call
} catch (err: any) {
expectCallErrorMsg(err?.message, expected)
return // force
}
expect.fail("Unexpected! Call didn't error!?")
}

export const expectCallErrorMsg = (actual: string, expected?: string) => {
// Match call error
expect(actual).to.deep.contain('Could not perform call')
// Match specific error
if (expected) expectSpecificMsg(actual, expected)
}

export const expectSpecificMsg = (actual: string, expected: string) => {
// The error message is displayed as a felt hex string, so we need to convert the text.
// ref: https://github.com/starkware-libs/cairo-lang/blob/c954f154bbab04c3fb27f7598b015a9475fc628e/src/starkware/starknet/business_logic/execution/execute_entry_point.py#L223
const expectedHex = '0x' + Buffer.from(expected, 'utf8').toString('hex')
const errorMessage = `Execution was reverted; failure reason: [${expectedHex}]`
if (!actual.includes(errorMessage)) {
expect.fail(`\nActual: ${actual}\n\nExpected:\n\tFelt hex: ${expectedHex}\n\tText: ${expected}`)
}
}

// Starknet v0.11.0 and higher only allow declaring a class once:
// https://github.com/starkware-libs/starknet-specs/pull/85
export const expectSuccessOrDeclared = async (declareContractPromise: Promise<any>) => {
try {
await declareContractPromise
} catch (err: any) {
if (/Class with hash 0x[0-9a-f]+ is already declared\./.test(err?.message)) {
return // force
}
expect.fail(err)
}
}

0 comments on commit 1015270

Please sign in to comment.