Skip to content

Commit

Permalink
Merging in origin/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
sdrug committed Mar 24, 2022
2 parents 0b92e98 + 6963a1d commit f251f04
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 26 deletions.
6 changes: 2 additions & 4 deletions contracts/programs/ocr2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use anchor_lang::prelude::*;
use anchor_lang::solana_program::sysvar::fees::Fees;
use anchor_spl::token;

use arrayref::{array_ref, array_refs};
Expand Down Expand Up @@ -777,9 +776,8 @@ impl Report {
fn calculate_reimbursement_gjuels(juels_per_lamport: u64, _signature_count: usize) -> Result<u64> {
const SIGNERS: u64 = 1;
const GIGA: u128 = 10u128.pow(9);
let fees = Fees::get()?;
let lamports_per_signature = fees.fee_calculator.lamports_per_signature;
let lamports = lamports_per_signature * SIGNERS;
const LAMPORTS_PER_SIGNATURE: u64 = 5_000; // constant, originally retrieved from deprecated sysvar fees
let lamports = LAMPORTS_PER_SIGNATURE * SIGNERS;
let juels = u128::from(lamports) * u128::from(juels_per_lamport);
let gjuels = juels / GIGA; // return value as gjuels

Expand Down
5 changes: 4 additions & 1 deletion examples/spec/ocr2-oracle-simple.spec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ juelsPerFeeCoinSource = """
ds1_coin_parse [type="jsonparse" path="result"]
ds1_coin -> ds1_coin_parse -> divide
divide [type="divide" input="$(ds1_link_parse)" divisor="$(ds1_coin_parse)" precision="9"]
// ds1_link_parse (dollars/LINK)
// ds1_coin_parse (dollars/SOL)
// ds1_coin_parse / ds1_link_parse = LINK/SOL
divide [type="divide" input="$(ds1_coin_parse)" divisor="$(ds1_link_parse)" precision="9"]
scale [type="multiply" times=1000000000]
divide -> scale
Expand Down
5 changes: 4 additions & 1 deletion examples/spec/ocr2-oracle.spec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ juelsPerFeeCoinSource = """
median_coin [type="median" values=<[ $(ds1_coin_parse), $(ds2_coin_parse), $(ds3_coin_parse) ]> allowedFaults=2]
// Divide and scale appropriately
divide [type="divide" input="$(median_link)" divisor="$(median_coin)" precision="9"]
// median_link (dollars/LINK)
// median_coin (dollars/SOL)
// median_coin / median_link = LINK/SOL
divide [type="divide" input="$(median_coin)" divisor="$(median_link)" precision="9"]
scale [type="multiply" times=1000000000]
median_link -> divide
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const wrapCommand = (command) => {
await prompt('CREATION,APPROVAL or EXECUTION TX will be executed. Continue?')
logger.loading(`Executing action...`)
const txhash = await this.sendTxWithIDL(this.signAndSendRawTx, this.program.idl)(rawTxs)
logger.success(`TX succeded at ${txhash}`)
await this.inspectProposalState(proposal)
return {
responses: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Result } from '@chainlink/gauntlet-core'
import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana'
import { ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'
import { CONTRACT_LIST } from '../../../lib/contracts'
import { isValidTokenAccount } from './utils'

export default class CreateAccount extends SolanaCommand {
static id = 'token:create_account'
static category = CONTRACT_LIST.TOKEN
static examples = ['yarn gauntlet token:create_account --network=devnet --address=<BASE_ADDRESS> <TOKEN>']

constructor(flags, args) {
super(flags, args)

this.requireFlag('address', `Provide an address from which the 'Token Associated Account' will be derived`)
this.require(!!args[0], 'Provide a token address')
}

execute = async () => {
const tokenAddress = new PublicKey(this.args[0])

const newAccountBase = new PublicKey(this.flags.address)
const associatedAcc = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
tokenAddress,
newAccountBase,
true,
)

const token = new Token(this.provider.connection, tokenAddress, TOKEN_PROGRAM_ID, {
publicKey: this.wallet.publicKey,
secretKey: Buffer.from([]),
})

const accountExists = await isValidTokenAccount(token, associatedAcc)
this.require(
!accountExists,
`A Token Associated Account to address ${newAccountBase.toString()} already exists: ${associatedAcc}`,
)

const ix = Token.createAssociatedTokenAccountInstruction(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
tokenAddress,
associatedAcc,
newAccountBase,
this.wallet.publicKey,
)

await prompt(`Continue to create new Token associated account to ${newAccountBase.toString()}`)
logger.loading('Creating account...')
const tx = await this.signAndSendRawTx([ix])

logger.success(`New account created at ${associatedAcc.toString()} on tx ${tx}`)

return {
responses: [
{
tx: this.wrapResponse(tx, this.args[0]),
contract: this.args[0],
},
],
} as Result<TransactionResponse>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Result } from '@chainlink/gauntlet-core'
import { logger, prompt, BN } from '@chainlink/gauntlet-core/dist/utils'
import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana'
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'
import { TOKEN_DECIMALS } from '../../../lib/constants'
import { CONTRACT_LIST } from '../../../lib/contracts'

export default class DeployToken extends SolanaCommand {
Expand All @@ -18,7 +19,7 @@ export default class DeployToken extends SolanaCommand {

logger.loading('Creating token...')

const decimals = this.flags.decimals || 9
const decimals = this.flags.decimals || TOKEN_DECIMALS
const token = await Token.createMint(
this.provider.connection,
this.wallet.payer,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import DeployToken from './deploy'
import CreateAccount from './createAccount'
import ReadState from './read'
import TransferToken from './transfer'
import * as tokenUtils from './utils'

export default [DeployToken, ReadState, TransferToken]
export default [DeployToken, ReadState, TransferToken, CreateAccount]

export { tokenUtils }
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Result } from '@chainlink/gauntlet-core'
import { logger, BN } from '@chainlink/gauntlet-core/dist/utils'
import { logger, BN, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana'
import { ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
import { TOKEN_DECIMALS } from '../../../lib/constants'
import { CONTRACT_LIST } from '../../../lib/contracts'
import { isValidTokenAccount } from './utils'

export default class TransferToken extends SolanaCommand {
static id = 'token:transfer'
Expand All @@ -20,28 +22,54 @@ export default class TransferToken extends SolanaCommand {
this.require(!!args[0], 'Provide a token address')
}

execute = async () => {
makeRawTransaction = async (signer: PublicKey) => {
const address = this.args[0]
const token = new Token(this.provider.connection, new PublicKey(address), TOKEN_PROGRAM_ID, this.wallet.payer)

const token = new Token(this.provider.connection, new PublicKey(address), TOKEN_PROGRAM_ID, {
publicKey: signer,
secretKey: Buffer.from([]),
})

const from = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
token.publicKey,
this.wallet.publicKey,
signer,
true,
)

const destination = new PublicKey(this.flags.to)
const amount = new BN(this.flags.amount).mul(new BN(10).pow(new BN(TOKEN_DECIMALS)))
this.require(
await isValidTokenAccount(token, destination),
`Destination ${destination.toString()} is not a valid token account`,
)

const destination = (await token.getOrCreateAssociatedAccountInfo(new PublicKey(this.flags.to))).address
const amount = new BN(this.flags.amount).toNumber()
logger.info(
`Preparing instruction to send ${amount.toString()} (${this.flags.amount}) Tokens to ${destination.toString()}`,
)
const ix = Token.createTransferInstruction(TOKEN_PROGRAM_ID, from, destination, signer, [], amount.toNumber())

return [
{
...ix,
// createTransferInstruction does not return the PublicKey type
keys: ix.keys.map((k) => ({ ...k, pubkey: new PublicKey(k.pubkey) })),
},
]
}

logger.loading(`Transferring ${amount} tokens to ${destination}...`)
const tx = await token.transfer(from, destination, this.wallet.payer, [], amount)
execute = async () => {
const rawTx = await this.makeRawTransaction(this.wallet.publicKey)
await prompt('Continue sending tokens?')
const txhash = await this.signAndSendRawTx(rawTx)
logger.success(`Tokens sent on tx hash: ${txhash}`)

return {
responses: [
{
tx: this.wrapResponse(tx, token.publicKey.toString()),
contract: token.publicKey.toString(),
tx: this.wrapResponse(txhash, this.args[0]),
contract: this.args[0],
},
],
} as Result<TransactionResponse>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Token } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'

export const isValidTokenAccount = async (token: Token, address: PublicKey) => {
try {
const info = await token.getAccountInfo(address)
return !!info.address
} catch (e) {
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ export const MAX_TRANSACTION_BYTES = 996
export const ORACLES_MAX_LENGTH = 19
export const UPGRADEABLE_BPF_LOADER_PROGRAM_ID = new PublicKey('BPFLoaderUpgradeab1e11111111111111111111111')
export const ADDITIONAL_STATE_BUFFER = 1024

// Solana uses u64 for token values. We couldn't fit the entire LINK supply with 18 decimals. Most tokens use 9 or even 8 decimals
export const TOKEN_DECIMALS = 9
15 changes: 11 additions & 4 deletions gauntlet/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,20 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"

"@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5":
"@babel/runtime@^7.10.4", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5":
version "7.16.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.10.5":
version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/template@^7.16.0", "@babel/template@^7.3.3":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6"
Expand Down Expand Up @@ -985,9 +992,9 @@
tweetnacl "^1.0.0"

"@solana/web3.js@^1.21.0":
version "1.31.0"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.31.0.tgz#7a313d4c1a90b77f27ddbfe845a10d6883e06452"
integrity sha512-7nHHx1JNFnrt15e9y8m38I/EJCbaB+bFC3KZVM1+QhybCikFxGMtGA5r7PDC3GEL1R2RZA8yKoLkDKo3vzzqnw==
version "1.35.1"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.35.1.tgz#777b039a3b51e63c347712a57c7db87c9d1db832"
integrity sha512-3bDawFFI0KcvgI8Ae4N4hdQ8+Bg9gu6q+IkhPrYxOF6RYnB3U+9A4u+DhHZWLvTvgoTyesi/m5HzlleKtFEqRQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@ethersproject/sha2" "^5.5.0"
Expand Down
5 changes: 4 additions & 1 deletion ops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ func JuelsSource(priceAdapter string) string {
sol2usd [type=bridge name=%s requestData=<{"data":{"from":"SOL", "to":"USD"}}>]
parseT [type="jsonparse" path="result"]
divide [type="divide" input="$(parseL)" divisor="$(parseT)" precision="9"]
// parseL (dollars/LINK)
// parseT (dollars/SOL)
// parseT / parseL = LINK/SOL
divide [type="divide" input="$(parseT)" divisor="$(parseL)" precision="9"]
scale [type="multiply" times=1000000000]
link2usd -> parseL -> divide
Expand Down
2 changes: 1 addition & 1 deletion pkg/solana/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func GetLatestTransmission(ctx context.Context, client *rpc.Client, account sola
// setup transmissionLen
transmissionLen := TransmissionLen

var transmissionOffset uint64 = AccountDiscriminatorLen + TransmissionsHeaderMaxSize + (uint64(cursor) * transmissionLen)
var transmissionOffset = AccountDiscriminatorLen + TransmissionsHeaderMaxSize + (uint64(cursor) * transmissionLen)

res, err = client.GetAccountInfoWithOpts(ctx, account, &rpc.GetAccountInfoOpts{
Encoding: "base64",
Expand Down
37 changes: 37 additions & 0 deletions pkg/solana/report_fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//go:build go1.18
// +build go1.18

package solana

import (
"math/big"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median"
)

// Ensure your env is using go 1.18 then in pkg/solana:
// go test -tags=go1.18 -fuzz ./...
func FuzzReportCodecMedianFromReport(f *testing.F) {
cdc := ReportCodec{}
report, err := cdc.BuildReport([]median.ParsedAttributedObservation{
{Timestamp: uint32(time.Now().Unix()), Value: big.NewInt(10), JuelsPerFeeCoin: big.NewInt(100000)},
{Timestamp: uint32(time.Now().Unix()), Value: big.NewInt(10), JuelsPerFeeCoin: big.NewInt(200000)},
{Timestamp: uint32(time.Now().Unix()), Value: big.NewInt(11), JuelsPerFeeCoin: big.NewInt(300000)}})
require.NoError(f, err)

// Seed with valid report
f.Add([]byte(report))
f.Fuzz(func(t *testing.T, report []byte) {
med, err := cdc.MedianFromReport(report)
if err == nil {
// Should always be able to build a report from the medians extracted
// Note however that juelsPerFeeCoin is only 8 bytes, so we can use the median for it
_, err = cdc.BuildReport([]median.ParsedAttributedObservation{{Timestamp: uint32(time.Now().Unix()), Value: med, JuelsPerFeeCoin: big.NewInt(100000)}})
require.NoError(t, err)
}
})
}
Loading

0 comments on commit f251f04

Please sign in to comment.