Fix subsidization and fees (#127)
* Revert "solana: partially revert #82 subsidization changes"
This reverts commit 2967653e
* fix subsidization
* fix deleted grpc tag dependency
* revert devnet changes
* verify system instruction action
* ┬─┬ノ(ಠ_ಠノ)
This commit is contained in:
parent
5997f133c3
commit
bec598b41a
|
@ -16,7 +16,6 @@ require (
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||||
github.com/golang/protobuf v1.4.3
|
github.com/golang/protobuf v1.4.3
|
||||||
github.com/golang/snappy v0.0.2 // indirect
|
github.com/golang/snappy v0.0.2 // indirect
|
||||||
github.com/googleapis/gnostic v0.5.3 // indirect
|
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.11 // indirect
|
github.com/imdario/mergo v0.3.11 // indirect
|
||||||
|
@ -60,12 +59,11 @@ require (
|
||||||
go.opencensus.io v0.22.5 // indirect
|
go.opencensus.io v0.22.5 // indirect
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
go.uber.org/zap v1.16.0
|
go.uber.org/zap v1.16.0
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d
|
|
||||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee
|
||||||
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 // indirect
|
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20201016160150-f659759dc4ca
|
golang.org/x/sys v0.0.0-20201016160150-f659759dc4ca
|
||||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154
|
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154
|
||||||
google.golang.org/grpc v1.33.0
|
google.golang.org/grpc v1.33.1
|
||||||
google.golang.org/protobuf v1.25.0
|
google.golang.org/protobuf v1.25.0
|
||||||
k8s.io/api v0.19.4
|
k8s.io/api v0.19.4
|
||||||
k8s.io/apimachinery v0.19.4
|
k8s.io/apimachinery v0.19.4
|
||||||
|
|
|
@ -224,6 +224,7 @@ github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt
|
||||||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
||||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
github.com/elastic/gosigar v0.10.5/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs=
|
github.com/elastic/gosigar v0.10.5/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs=
|
||||||
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||||
|
@ -380,8 +381,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
|
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
|
||||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||||
github.com/googleapis/gnostic v0.5.3 h1:2qsuRm+bzgwSIKikigPASa2GhW8H2Dn4Qq7UxD8K/48=
|
|
||||||
github.com/googleapis/gnostic v0.5.3/go.mod h1:TRWw1s4gxBGjSe301Dai3c7wXJAZy57+/6tawkOvqHQ=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
@ -1147,7 +1146,6 @@ github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1
|
||||||
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw=
|
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw=
|
||||||
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM=
|
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM=
|
||||||
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
|
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
|
||||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
|
||||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||||
|
@ -1563,8 +1561,8 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.33.0 h1:IBKSUNL2uBS2DkJBncPP+TwT0sp9tgA8A75NjHt6umg=
|
google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
|
||||||
google.golang.org/grpc v1.33.0/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
@ -1623,8 +1621,6 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
@ -58,6 +58,9 @@ Burns a wrapped asset `token` from `sender` on the Solana chain.
|
||||||
|
|
||||||
The transfer proposal will be tracked at a new account `proposal` where VAAs will be submitted by guardians.
|
The transfer proposal will be tracked at a new account `proposal` where VAAs will be submitted by guardians.
|
||||||
|
|
||||||
|
This instruction needs to be preceded by a SOL Transfer instruction that transfers the fee to the BridgeConfig.
|
||||||
|
The fee can be calculated using the rules explained in the protocol documentation and `Bridge::transfer_fee()`.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
||||||
| Index | Name | Type | signer | writeable | empty | derived |
|
| Index | Name | Type | signer | writeable | empty | derived |
|
||||||
|
@ -66,12 +69,13 @@ Parameters:
|
||||||
| 1 | sys | SystemProgram | | | | |
|
| 1 | sys | SystemProgram | | | | |
|
||||||
| 2 | token_program | SplToken | | | | |
|
| 2 | token_program | SplToken | | | | |
|
||||||
| 3 | rent | Sysvar | | | | ✅ |
|
| 3 | rent | Sysvar | | | | ✅ |
|
||||||
| 4 | clock | Sysvar | | | ✅ | |
|
| 4 | clock | Sysvar | | | | ✅ |
|
||||||
| 5 | token_account | TokenAccount | | ✅ | | |
|
| 5 | instructions | Sysvar | | | | ✅ |
|
||||||
| 6 | bridge | BridgeConfig | | | | |
|
| 6 | token_account | TokenAccount | | ✅ | | |
|
||||||
| 7 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
| 7 | bridge | BridgeConfig | | | | |
|
||||||
| 8 | token | WrappedAsset | | ✅ | | ✅ |
|
| 8 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
||||||
| 9 | payer | Account | ✅ | | | |
|
| 9 | token | WrappedAsset | | ✅ | | ✅ |
|
||||||
|
| 10 | payer | Account | ✅ | | | |
|
||||||
|
|
||||||
#### TransferOutNative
|
#### TransferOutNative
|
||||||
|
|
||||||
|
@ -80,6 +84,9 @@ Locks a Solana native token (spl-token) `token` from `sender` on the Solana chai
|
||||||
|
|
||||||
The transfer proposal will be tracked at a new account `proposal` where a VAA will be submitted by guardians.
|
The transfer proposal will be tracked at a new account `proposal` where a VAA will be submitted by guardians.
|
||||||
|
|
||||||
|
This instruction needs to be preceded by a SOL Transfer instruction that transfers the fee to the BridgeConfig.
|
||||||
|
The fee can be calculated using the rules explained in the protocol documentation and `Bridge::transfer_fee()`.
|
||||||
|
|
||||||
| Index | Name | Type | signer | writeable | empty | derived |
|
| Index | Name | Type | signer | writeable | empty | derived |
|
||||||
| ----- | --------------- | ------------------- | ------ | --------- | ----- | ------- |
|
| ----- | --------------- | ------------------- | ------ | --------- | ----- | ------- |
|
||||||
| 0 | bridge_p | BridgeProgram | | | | |
|
| 0 | bridge_p | BridgeProgram | | | | |
|
||||||
|
@ -87,12 +94,13 @@ The transfer proposal will be tracked at a new account `proposal` where a VAA wi
|
||||||
| 2 | token_program | SplToken | | | | |
|
| 2 | token_program | SplToken | | | | |
|
||||||
| 3 | rent | Sysvar | | | | ✅ |
|
| 3 | rent | Sysvar | | | | ✅ |
|
||||||
| 4 | clock | Sysvar | | | | ✅ |
|
| 4 | clock | Sysvar | | | | ✅ |
|
||||||
| 5 | token_account | TokenAccount | | ✅ | | |
|
| 5 | instructions | Sysvar | | | | ✅ |
|
||||||
| 6 | bridge | BridgeConfig | | | | |
|
| 6 | token_account | TokenAccount | | ✅ | | |
|
||||||
| 7 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
| 7 | bridge | BridgeConfig | | | | |
|
||||||
| 8 | token | Mint | | ✅ | | |
|
| 8 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
||||||
| 9 | payer | Account | ✅ | | | |
|
| 9 | token | Mint | | ✅ | | |
|
||||||
| 10 | custody_account | TokenAccount | | ✅ | opt | ✅ |
|
| 10 | payer | Account | ✅ | | | |
|
||||||
|
| 11 | custody_account | TokenAccount | | ✅ | opt | ✅ |
|
||||||
|
|
||||||
#### EvictTransferOut
|
#### EvictTransferOut
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,9 @@ pub enum Error {
|
||||||
/// Mismatching guardian set
|
/// Mismatching guardian set
|
||||||
#[error("GuardianSetMismatch")]
|
#[error("GuardianSetMismatch")]
|
||||||
GuardianSetMismatch,
|
GuardianSetMismatch,
|
||||||
|
/// Insufficient fees
|
||||||
|
#[error("InsufficientFees")]
|
||||||
|
InsufficientFees,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for ProgramError {
|
impl From<Error> for ProgramError {
|
||||||
|
|
|
@ -39,6 +39,7 @@ impl PrintProgramError for Error {
|
||||||
Error::CannotWrapNative => info!("Error: CannotWrapNative"),
|
Error::CannotWrapNative => info!("Error: CannotWrapNative"),
|
||||||
Error::VAAAlreadySubmitted => info!("Error: VAAAlreadySubmitted"),
|
Error::VAAAlreadySubmitted => info!("Error: VAAAlreadySubmitted"),
|
||||||
Error::GuardianSetMismatch => info!("Error: GuardianSetMismatch"),
|
Error::GuardianSetMismatch => info!("Error: GuardianSetMismatch"),
|
||||||
|
Error::InsufficientFees => info!("Error: InsufficientFees"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,7 @@ impl BridgeInstruction {
|
||||||
output.resize(size_of::<InitializePayload>() + 1, 0);
|
output.resize(size_of::<InitializePayload>() + 1, 0);
|
||||||
output[0] = 0;
|
output[0] = 0;
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
let value = unsafe {
|
let value = unsafe {
|
||||||
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut InitializePayload)
|
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut InitializePayload)
|
||||||
};
|
};
|
||||||
*value = payload;
|
*value = payload;
|
||||||
|
@ -205,7 +205,7 @@ impl BridgeInstruction {
|
||||||
output.resize(size_of::<TransferOutPayloadRaw>() + 1, 0);
|
output.resize(size_of::<TransferOutPayloadRaw>() + 1, 0);
|
||||||
output[0] = 1;
|
output[0] = 1;
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
let value = unsafe {
|
let value = unsafe {
|
||||||
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut TransferOutPayloadRaw)
|
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut TransferOutPayloadRaw)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -224,7 +224,7 @@ impl BridgeInstruction {
|
||||||
output.resize(1, 0);
|
output.resize(1, 0);
|
||||||
output[0] = 2;
|
output[0] = 2;
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
output.extend_from_slice(&payload);
|
output.extend_from_slice(&payload);
|
||||||
}
|
}
|
||||||
Self::EvictTransferOut() => {
|
Self::EvictTransferOut() => {
|
||||||
output.resize(1, 0);
|
output.resize(1, 0);
|
||||||
|
@ -242,7 +242,7 @@ impl BridgeInstruction {
|
||||||
output.resize(size_of::<VerifySigPayload>() + 1, 0);
|
output.resize(size_of::<VerifySigPayload>() + 1, 0);
|
||||||
output[0] = 6;
|
output[0] = 6;
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
let value = unsafe {
|
let value = unsafe {
|
||||||
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VerifySigPayload)
|
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VerifySigPayload)
|
||||||
};
|
};
|
||||||
*value = payload;
|
*value = payload;
|
||||||
|
@ -251,7 +251,7 @@ impl BridgeInstruction {
|
||||||
output.resize(size_of::<AssetMeta>() + 1, 0);
|
output.resize(size_of::<AssetMeta>() + 1, 0);
|
||||||
output[0] = 7;
|
output[0] = 7;
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
let value =
|
let value =
|
||||||
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut AssetMeta) };
|
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut AssetMeta) };
|
||||||
*value = payload;
|
*value = payload;
|
||||||
}
|
}
|
||||||
|
@ -280,7 +280,7 @@ pub fn initialize(
|
||||||
len_guardians: initial_guardian.len() as u8,
|
len_guardians: initial_guardian.len() as u8,
|
||||||
initial_guardian: initial_g,
|
initial_guardian: initial_g,
|
||||||
})
|
})
|
||||||
.serialize()?;
|
.serialize()?;
|
||||||
|
|
||||||
let bridge_key = Bridge::derive_bridge_id(program_id)?;
|
let bridge_key = Bridge::derive_bridge_id(program_id)?;
|
||||||
let guardian_set_key = Bridge::derive_guardian_set_id(program_id, &bridge_key, 0)?;
|
let guardian_set_key = Bridge::derive_guardian_set_id(program_id, &bridge_key, 0)?;
|
||||||
|
@ -329,6 +329,7 @@ pub fn transfer_out(
|
||||||
AccountMeta::new_readonly(spl_token::id(), false),
|
AccountMeta::new_readonly(spl_token::id(), false),
|
||||||
AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
|
AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
|
||||||
AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
|
AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
|
||||||
|
AccountMeta::new_readonly(solana_program::sysvar::instructions::id(), false),
|
||||||
AccountMeta::new(*token_account, false),
|
AccountMeta::new(*token_account, false),
|
||||||
AccountMeta::new_readonly(bridge_key, false),
|
AccountMeta::new_readonly(bridge_key, false),
|
||||||
AccountMeta::new(transfer_key, false),
|
AccountMeta::new(transfer_key, false),
|
||||||
|
@ -368,6 +369,7 @@ pub fn verify_signatures(
|
||||||
AccountMeta::new_readonly(*program_id, false),
|
AccountMeta::new_readonly(*program_id, false),
|
||||||
AccountMeta::new_readonly(solana_program::system_program::id(), false),
|
AccountMeta::new_readonly(solana_program::system_program::id(), false),
|
||||||
AccountMeta::new_readonly(solana_program::sysvar::instructions::id(), false),
|
AccountMeta::new_readonly(solana_program::sysvar::instructions::id(), false),
|
||||||
|
AccountMeta::new(bridge_key, false),
|
||||||
AccountMeta::new(*signature_acc, false),
|
AccountMeta::new(*signature_acc, false),
|
||||||
AccountMeta::new_readonly(guardian_set_key, false),
|
AccountMeta::new_readonly(guardian_set_key, false),
|
||||||
AccountMeta::new(*payer, true),
|
AccountMeta::new(*payer, true),
|
||||||
|
@ -530,6 +532,6 @@ pub fn unpack<T>(input: &[u8]) -> Result<&T, ProgramError> {
|
||||||
return Err(ProgramError::InvalidInstructionData);
|
return Err(ProgramError::InvalidInstructionData);
|
||||||
}
|
}
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
let val: &T = unsafe { &*(&input[1] as *const u8 as *const T) };
|
let val: &T = unsafe { &*(&input[1] as *const u8 as *const T) };
|
||||||
Ok(val)
|
Ok(val)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,6 @@ use std::borrow::BorrowMut;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use solana_program::fee_calculator::FeeCalculator;
|
use solana_program::fee_calculator::FeeCalculator;
|
||||||
|
|
||||||
/// Tx fee of Signature checks and PostVAA (see docs for calculation)
|
|
||||||
const VAA_TX_FEE: u64 = 18 * 10000;
|
|
||||||
|
|
||||||
/// SigInfo contains metadata about signers in a VerifySignature ix
|
/// SigInfo contains metadata about signers in a VerifySignature ix
|
||||||
struct SigInfo {
|
struct SigInfo {
|
||||||
/// index of the signer in the guardianset
|
/// index of the signer in the guardianset
|
||||||
|
@ -127,9 +124,10 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
new_bridge_info.key,
|
new_bridge_info.key,
|
||||||
payer_info.key,
|
payer_info,
|
||||||
program_id,
|
program_id,
|
||||||
&bridge_seed,
|
&bridge_seed,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut new_account_data = new_bridge_info.try_borrow_mut_data()?;
|
let mut new_account_data = new_bridge_info.try_borrow_mut_data()?;
|
||||||
|
@ -144,9 +142,10 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
new_guardian_info.key,
|
new_guardian_info.key,
|
||||||
payer_info.key,
|
payer_info,
|
||||||
program_id,
|
program_id,
|
||||||
&guardian_seed,
|
&guardian_seed,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut new_guardian_data = new_guardian_info.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?;
|
let mut new_guardian_data = new_guardian_info.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?;
|
||||||
|
@ -201,6 +200,7 @@ impl Bridge {
|
||||||
next_account_info(account_info_iter)?; // Bridge program
|
next_account_info(account_info_iter)?; // Bridge program
|
||||||
next_account_info(account_info_iter)?; // System program
|
next_account_info(account_info_iter)?; // System program
|
||||||
let instruction_accounts = next_account_info(account_info_iter)?;
|
let instruction_accounts = next_account_info(account_info_iter)?;
|
||||||
|
let bridge_info = next_account_info(account_info_iter)?;
|
||||||
let sig_info = next_account_info(account_info_iter)?;
|
let sig_info = next_account_info(account_info_iter)?;
|
||||||
let guardian_set_info = next_account_info(account_info_iter)?;
|
let guardian_set_info = next_account_info(account_info_iter)?;
|
||||||
let payer_info = next_account_info(account_info_iter)?;
|
let payer_info = next_account_info(account_info_iter)?;
|
||||||
|
@ -315,9 +315,10 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
sig_info.key,
|
sig_info.key,
|
||||||
payer_info.key,
|
payer_info,
|
||||||
program_id,
|
program_id,
|
||||||
&sig_seeds,
|
&sig_seeds,
|
||||||
|
Some(bridge_info),
|
||||||
)?;
|
)?;
|
||||||
} else if payload.initial_creation {
|
} else if payload.initial_creation {
|
||||||
return Err(Error::AlreadyExists.into());
|
return Err(Error::AlreadyExists.into());
|
||||||
|
@ -375,6 +376,7 @@ impl Bridge {
|
||||||
next_account_info(account_info_iter)?; // Token program
|
next_account_info(account_info_iter)?; // Token program
|
||||||
next_account_info(account_info_iter)?; // Rent sysvar
|
next_account_info(account_info_iter)?; // Rent sysvar
|
||||||
let clock_info = next_account_info(account_info_iter)?;
|
let clock_info = next_account_info(account_info_iter)?;
|
||||||
|
let instructions_info = next_account_info(account_info_iter)?;
|
||||||
let sender_account_info = next_account_info(account_info_iter)?;
|
let sender_account_info = next_account_info(account_info_iter)?;
|
||||||
let bridge_info = next_account_info(account_info_iter)?;
|
let bridge_info = next_account_info(account_info_iter)?;
|
||||||
let transfer_info = next_account_info(account_info_iter)?;
|
let transfer_info = next_account_info(account_info_iter)?;
|
||||||
|
@ -387,6 +389,10 @@ impl Bridge {
|
||||||
let mint = Bridge::mint_deserialize(mint_info)?;
|
let mint = Bridge::mint_deserialize(mint_info)?;
|
||||||
let clock = Clock::from_account_info(clock_info)?;
|
let clock = Clock::from_account_info(clock_info)?;
|
||||||
|
|
||||||
|
// Fee handling
|
||||||
|
let fee = Self::transfer_fee();
|
||||||
|
Self::check_fees(instructions_info, bridge_info, fee)?;
|
||||||
|
|
||||||
// Does the token belong to the mint
|
// Does the token belong to the mint
|
||||||
if sender.mint != *mint_info.key {
|
if sender.mint != *mint_info.key {
|
||||||
return Err(Error::TokenMintMismatch.into());
|
return Err(Error::TokenMintMismatch.into());
|
||||||
|
@ -418,9 +424,10 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
transfer_info.key,
|
transfer_info.key,
|
||||||
payer_info.key,
|
payer_info,
|
||||||
program_id,
|
program_id,
|
||||||
&transfer_seed,
|
&transfer_seed,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Load transfer account
|
// Load transfer account
|
||||||
|
@ -469,6 +476,7 @@ impl Bridge {
|
||||||
next_account_info(account_info_iter)?; // Token program
|
next_account_info(account_info_iter)?; // Token program
|
||||||
next_account_info(account_info_iter)?; // Rent sysvar
|
next_account_info(account_info_iter)?; // Rent sysvar
|
||||||
let clock_info = next_account_info(account_info_iter)?;
|
let clock_info = next_account_info(account_info_iter)?;
|
||||||
|
let instructions_info = next_account_info(account_info_iter)?;
|
||||||
let sender_account_info = next_account_info(account_info_iter)?;
|
let sender_account_info = next_account_info(account_info_iter)?;
|
||||||
let bridge_info = next_account_info(account_info_iter)?;
|
let bridge_info = next_account_info(account_info_iter)?;
|
||||||
let transfer_info = next_account_info(account_info_iter)?;
|
let transfer_info = next_account_info(account_info_iter)?;
|
||||||
|
@ -482,6 +490,8 @@ impl Bridge {
|
||||||
let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?;
|
let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?;
|
||||||
let clock = Clock::from_account_info(clock_info)?;
|
let clock = Clock::from_account_info(clock_info)?;
|
||||||
|
|
||||||
|
let fee = Self::transfer_fee();
|
||||||
|
Self::check_fees(instructions_info, bridge_info, fee)?;
|
||||||
|
|
||||||
// Does the token belong to the mint
|
// Does the token belong to the mint
|
||||||
if sender.mint != *mint_info.key {
|
if sender.mint != *mint_info.key {
|
||||||
|
@ -502,9 +512,10 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
transfer_info.key,
|
transfer_info.key,
|
||||||
payer_info.key,
|
payer_info,
|
||||||
program_id,
|
program_id,
|
||||||
&transfer_seed,
|
&transfer_seed,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Load transfer account
|
// Load transfer account
|
||||||
|
@ -527,7 +538,8 @@ impl Bridge {
|
||||||
bridge_info.key,
|
bridge_info.key,
|
||||||
custody_info.key,
|
custody_info.key,
|
||||||
mint_info.key,
|
mint_info.key,
|
||||||
payer_info.key,
|
payer_info,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,6 +582,77 @@ impl Bridge {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify that a certain fee was sent to the bridge in the preceding instruction
|
||||||
|
pub fn check_fees(instructions_info: &AccountInfo, bridge_info: &AccountInfo, fee: u64) -> Result<(), ProgramError> {
|
||||||
|
let current_instruction = solana_program::sysvar::instructions::load_current_index(
|
||||||
|
&instructions_info.try_borrow_mut_data()?,
|
||||||
|
);
|
||||||
|
if current_instruction == 0 {
|
||||||
|
return Err(ProgramError::InvalidInstructionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The previous ix must be a transfer instruction
|
||||||
|
let transfer_ix_index = (current_instruction - 1) as u8;
|
||||||
|
let transfer_ix = solana_program::sysvar::instructions::load_instruction_at(
|
||||||
|
transfer_ix_index as usize,
|
||||||
|
&instructions_info.try_borrow_mut_data()?,
|
||||||
|
)
|
||||||
|
.map_err(|_| ProgramError::InvalidAccountData)?;
|
||||||
|
|
||||||
|
// Check that the instruction is actually for the system program
|
||||||
|
if transfer_ix.program_id != solana_program::system_program::id() {
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
if transfer_ix.accounts.len() != 2 {
|
||||||
|
return Err(ProgramError::InvalidInstructionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the fee was transferred to the bridge config.
|
||||||
|
// We only care that the fee was sent to the bridge, not by whom it was sent.
|
||||||
|
if transfer_ix.accounts[1].pubkey != *bridge_info.key {
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The transfer instruction is serialized using bincode (little endian)
|
||||||
|
// uint32 ix_type = 2 (Transfer)
|
||||||
|
// uint64 lamports
|
||||||
|
// LEN: 4 + 8 = 12 bytes
|
||||||
|
if transfer_ix.data.len() != 12 {
|
||||||
|
return Err(ProgramError::InvalidAccountData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify action
|
||||||
|
if transfer_ix.data[..4] != [2, 0, 0, 0] {
|
||||||
|
return Err(ProgramError::InvalidInstructionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse amount
|
||||||
|
let mut fixed_data = [0u8; 8];
|
||||||
|
fixed_data.copy_from_slice(&transfer_ix.data[4..]);
|
||||||
|
let amount = u64::from_le_bytes(fixed_data);
|
||||||
|
|
||||||
|
// Verify fee amount
|
||||||
|
if amount < fee {
|
||||||
|
return Err(Error::InsufficientFees.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_sol(
|
||||||
|
payer_account: &AccountInfo,
|
||||||
|
recipient_account: &AccountInfo,
|
||||||
|
amount: u64,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let mut payer_balance = payer_account.try_borrow_mut_lamports()?;
|
||||||
|
**payer_balance = payer_balance.checked_sub(amount).ok_or(ProgramError::InsufficientFunds)?;
|
||||||
|
let mut recipient_balance = recipient_account.try_borrow_mut_lamports()?;
|
||||||
|
**recipient_balance = recipient_balance.checked_add(amount).ok_or(ProgramError::InvalidArgument)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Processes a VAA
|
/// Processes a VAA
|
||||||
pub fn process_vaa(
|
pub fn process_vaa(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
|
@ -601,17 +684,6 @@ impl Bridge {
|
||||||
return Err(Error::InvalidDerivedAccount.into());
|
return Err(Error::InvalidDerivedAccount.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and create claim
|
|
||||||
let claim_seeds = Bridge::derive_claim_seeds(bridge_info.key, vaa.signature_body()?);
|
|
||||||
Bridge::check_and_create_account::<ClaimedVAA>(
|
|
||||||
program_id,
|
|
||||||
accounts,
|
|
||||||
claim_info.key,
|
|
||||||
payer_info.key,
|
|
||||||
program_id,
|
|
||||||
&claim_seeds,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Check that the guardian set is still active
|
// Check that the guardian set is still active
|
||||||
if (guardian_set.expiration_time as i64) > clock.unix_timestamp {
|
if (guardian_set.expiration_time as i64) > clock.unix_timestamp {
|
||||||
return Err(Error::GuardianSetExpired.into());
|
return Err(Error::GuardianSetExpired.into());
|
||||||
|
@ -643,12 +715,14 @@ impl Bridge {
|
||||||
return Err(ProgramError::InvalidArgument);
|
return Err(ProgramError::InvalidArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut evict_signatures = false;
|
||||||
let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?;
|
let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?;
|
||||||
match payload {
|
match payload {
|
||||||
VAABody::UpdateGuardianSet(v) => {
|
VAABody::UpdateGuardianSet(v) => {
|
||||||
let mut bridge_data = bridge_info.try_borrow_mut_data()?;
|
let mut bridge_data = bridge_info.try_borrow_mut_data()?;
|
||||||
let bridge: &mut Bridge = Self::unpack(&mut bridge_data)?;
|
let bridge: &mut Bridge = Self::unpack(&mut bridge_data)?;
|
||||||
|
|
||||||
|
evict_signatures = true;
|
||||||
Self::process_vaa_set_update(
|
Self::process_vaa_set_update(
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
|
@ -675,6 +749,7 @@ impl Bridge {
|
||||||
} else {
|
} else {
|
||||||
let bridge_data = bridge_info.try_borrow_data()?;
|
let bridge_data = bridge_info.try_borrow_data()?;
|
||||||
let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?;
|
let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?;
|
||||||
|
evict_signatures = true;
|
||||||
Self::process_vaa_transfer(
|
Self::process_vaa_transfer(
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
|
@ -687,6 +762,29 @@ impl Bridge {
|
||||||
}
|
}
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
// Check and create claim
|
||||||
|
let claim_seeds = Bridge::derive_claim_seeds(bridge_info.key, vaa.signature_body()?);
|
||||||
|
Bridge::check_and_create_account::<ClaimedVAA>(
|
||||||
|
program_id,
|
||||||
|
accounts,
|
||||||
|
claim_info.key,
|
||||||
|
payer_info,
|
||||||
|
program_id,
|
||||||
|
&claim_seeds,
|
||||||
|
Some(bridge_info),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// If the signatures are not needed anymore, evict them and reclaim rent.
|
||||||
|
// This should cover most of the costs of the guardian.
|
||||||
|
if evict_signatures {
|
||||||
|
Self::transfer_sol(sig_info, payer_info, sig_info.lamports())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refund tx fee if possible
|
||||||
|
if bridge_info.lamports().checked_sub(Self::MIN_BRIDGE_BALANCE).unwrap_or(0) >= Self::VAA_TX_FEE {
|
||||||
|
Self::transfer_sol(bridge_info, payer_info, Self::VAA_TX_FEE)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Load claim account
|
// Load claim account
|
||||||
let mut claim_data = claim_info.try_borrow_mut_data()?;
|
let mut claim_data = claim_info.try_borrow_mut_data()?;
|
||||||
let claim: &mut ClaimedVAA = Bridge::unpack_unchecked(&mut claim_data)?;
|
let claim: &mut ClaimedVAA = Bridge::unpack_unchecked(&mut claim_data)?;
|
||||||
|
@ -736,9 +834,10 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
new_guardian_info.key,
|
new_guardian_info.key,
|
||||||
payer_info.key,
|
payer_info,
|
||||||
program_id,
|
program_id,
|
||||||
&guardian_seed,
|
&guardian_seed,
|
||||||
|
Some(bridge_info),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut guardian_set_new_data = new_guardian_info.try_borrow_mut_data()?;
|
let mut guardian_set_new_data = new_guardian_info.try_borrow_mut_data()?;
|
||||||
|
@ -928,9 +1027,10 @@ impl Bridge {
|
||||||
&bridge.config.token_program,
|
&bridge.config.token_program,
|
||||||
mint_info.key,
|
mint_info.key,
|
||||||
bridge_info.key,
|
bridge_info.key,
|
||||||
payer_info.key,
|
payer_info,
|
||||||
&a,
|
&a,
|
||||||
a.decimals,
|
a.decimals,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Check and create wrapped asset meta to allow reverse resolution of info
|
// Check and create wrapped asset meta to allow reverse resolution of info
|
||||||
|
@ -939,9 +1039,10 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
wrapped_meta_info.key,
|
wrapped_meta_info.key,
|
||||||
payer_info.key,
|
payer_info,
|
||||||
program_id,
|
program_id,
|
||||||
&wrapped_meta_seeds,
|
&wrapped_meta_seeds,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut wrapped_meta_data = wrapped_meta_info.try_borrow_mut_data()?;
|
let mut wrapped_meta_data = wrapped_meta_info.try_borrow_mut_data()?;
|
||||||
|
@ -1046,7 +1147,8 @@ impl Bridge {
|
||||||
bridge: &Pubkey,
|
bridge: &Pubkey,
|
||||||
account: &Pubkey,
|
account: &Pubkey,
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
payer: &Pubkey,
|
payer: &AccountInfo,
|
||||||
|
subsidizer: Option<&AccountInfo>,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
Self::check_and_create_account::<[u8; spl_token::state::Account::LEN]>(
|
Self::check_and_create_account::<[u8; spl_token::state::Account::LEN]>(
|
||||||
program_id,
|
program_id,
|
||||||
|
@ -1055,6 +1157,7 @@ impl Bridge {
|
||||||
payer,
|
payer,
|
||||||
token_program,
|
token_program,
|
||||||
&Self::derive_custody_seeds(bridge, mint),
|
&Self::derive_custody_seeds(bridge, mint),
|
||||||
|
subsidizer,
|
||||||
)?;
|
)?;
|
||||||
info!(token_program.to_string().as_str());
|
info!(token_program.to_string().as_str());
|
||||||
let ix = spl_token::instruction::initialize_account(
|
let ix = spl_token::instruction::initialize_account(
|
||||||
|
@ -1073,9 +1176,10 @@ impl Bridge {
|
||||||
token_program: &Pubkey,
|
token_program: &Pubkey,
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
bridge: &Pubkey,
|
bridge: &Pubkey,
|
||||||
payer: &Pubkey,
|
payer: &AccountInfo,
|
||||||
asset: &AssetMeta,
|
asset: &AssetMeta,
|
||||||
decimals: u8,
|
decimals: u8,
|
||||||
|
subsidizer: Option<&AccountInfo>,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
Self::check_and_create_account::<[u8; spl_token::state::Mint::LEN]>(
|
Self::check_and_create_account::<[u8; spl_token::state::Mint::LEN]>(
|
||||||
program_id,
|
program_id,
|
||||||
|
@ -1084,6 +1188,7 @@ impl Bridge {
|
||||||
payer,
|
payer,
|
||||||
token_program,
|
token_program,
|
||||||
&Self::derive_wrapped_asset_seeds(bridge, asset.chain, asset.decimals, asset.address),
|
&Self::derive_wrapped_asset_seeds(bridge, asset.chain, asset.decimals, asset.address),
|
||||||
|
subsidizer,
|
||||||
)?;
|
)?;
|
||||||
let ix = spl_token::instruction::initialize_mint(
|
let ix = spl_token::instruction::initialize_mint(
|
||||||
token_program,
|
token_program,
|
||||||
|
@ -1115,14 +1220,21 @@ impl Bridge {
|
||||||
invoke_signed(instruction, account_infos, &[s.as_slice()])
|
invoke_signed(instruction, account_infos, &[s.as_slice()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The amount of sol that needs to be held in the BridgeConfig account in order to make it
|
||||||
|
/// exempt of rent payments.
|
||||||
|
const MIN_BRIDGE_BALANCE: u64 = (((solana_program::rent::ACCOUNT_STORAGE_OVERHEAD + size_of::<BridgeConfig>() as u64) *
|
||||||
|
solana_program::rent::DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64
|
||||||
|
* solana_program::rent::DEFAULT_EXEMPTION_THRESHOLD) as u64;
|
||||||
|
|
||||||
/// Check that a key was derived correctly and create account
|
/// Check that a key was derived correctly and create account
|
||||||
pub fn check_and_create_account<T: Sized>(
|
pub fn check_and_create_account<T: Sized>(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
new_account: &Pubkey,
|
new_account: &Pubkey,
|
||||||
payer: &Pubkey,
|
payer: &AccountInfo,
|
||||||
owner: &Pubkey,
|
owner: &Pubkey,
|
||||||
seeds: &Vec<Vec<u8>>,
|
seeds: &Vec<Vec<u8>>,
|
||||||
|
subsidizer: Option<&AccountInfo>,
|
||||||
) -> Result<Vec<Vec<u8>>, ProgramError> {
|
) -> Result<Vec<Vec<u8>>, ProgramError> {
|
||||||
info!("deriving key");
|
info!("deriving key");
|
||||||
let (expected_key, full_seeds) = Bridge::derive_key(program_id, seeds)?;
|
let (expected_key, full_seeds) = Bridge::derive_key(program_id, seeds)?;
|
||||||
|
@ -1135,11 +1247,27 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
new_account,
|
new_account,
|
||||||
payer,
|
payer.key,
|
||||||
owner,
|
owner,
|
||||||
&full_seeds,
|
&full_seeds,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// The subsidizer refunds the rent that needs to be paid to create the account.
|
||||||
|
// This mechanism is intended to reduce the cost of operating a guardian.
|
||||||
|
// The subsidizer account should be of the type BridgeConfig and will only pay out
|
||||||
|
// the subsidy if the account holds at least MIN_BRIDGE_BALANCE+rent
|
||||||
|
match subsidizer {
|
||||||
|
None => {}
|
||||||
|
Some(v) => {
|
||||||
|
let bal = v.try_lamports()?;
|
||||||
|
let rent = Rent::default().minimum_balance(size_of::<T>());
|
||||||
|
if bal.checked_sub(Self::MIN_BRIDGE_BALANCE).ok_or(ProgramError::InsufficientFunds)? >= rent {
|
||||||
|
// Refund rent to payer
|
||||||
|
Self::transfer_sol(v, payer, rent)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(full_seeds)
|
Ok(full_seeds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::{
|
||||||
vaa::BodyTransfer,
|
vaa::BodyTransfer,
|
||||||
};
|
};
|
||||||
use solana_program::program_pack::Pack;
|
use solana_program::program_pack::Pack;
|
||||||
|
use solana_program::rent::Rent;
|
||||||
|
|
||||||
/// fee rate as a ratio
|
/// fee rate as a ratio
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -233,7 +234,7 @@ impl Bridge {
|
||||||
return Err(ProgramError::InvalidAccountData);
|
return Err(ProgramError::InvalidAccountData);
|
||||||
}
|
}
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
Ok(unsafe { &mut *(&mut input[0] as *mut u8 as *mut T) })
|
Ok(unsafe { &mut *(&mut input[0] as *mut u8 as *mut T) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unpacks a state from a bytes buffer while assuring that the state is initialized.
|
/// Unpacks a state from a bytes buffer while assuring that the state is initialized.
|
||||||
|
@ -251,7 +252,7 @@ impl Bridge {
|
||||||
return Err(ProgramError::InvalidAccountData);
|
return Err(ProgramError::InvalidAccountData);
|
||||||
}
|
}
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
Ok(unsafe { &*(&input[0] as *const u8 as *const T) })
|
Ok(unsafe { &*(&input[0] as *const u8 as *const T) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,7 +325,7 @@ impl Bridge {
|
||||||
vec!["claim".as_bytes().to_vec(), bridge.to_bytes().to_vec()],
|
vec!["claim".as_bytes().to_vec(), bridge.to_bytes().to_vec()],
|
||||||
body.chunks(32).map(|v| v.to_vec()).collect(),
|
body.chunks(32).map(|v| v.to_vec()).collect(),
|
||||||
]
|
]
|
||||||
.concat()
|
.concat()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates derived seeds for a wrapped asset meta entry
|
/// Calculates derived seeds for a wrapped asset meta entry
|
||||||
|
@ -392,7 +393,7 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
&Self::derive_guardian_set_seeds(bridge_key, guardian_set_index),
|
&Self::derive_guardian_set_seeds(bridge_key, guardian_set_index),
|
||||||
)?
|
)?
|
||||||
.0)
|
.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates a derived seeds for a wrapped asset
|
/// Calculates a derived seeds for a wrapped asset
|
||||||
|
@ -407,7 +408,7 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
&Self::derive_wrapped_asset_seeds(bridge_key, asset_chain, asset_decimal, asset),
|
&Self::derive_wrapped_asset_seeds(bridge_key, asset_chain, asset_decimal, asset),
|
||||||
)?
|
)?
|
||||||
.0)
|
.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates a derived address for a transfer out
|
/// Calculates a derived address for a transfer out
|
||||||
|
@ -433,7 +434,7 @@ impl Bridge {
|
||||||
slot,
|
slot,
|
||||||
),
|
),
|
||||||
)?
|
)?
|
||||||
.0)
|
.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates derived address for a signature account
|
/// Calculates derived address for a signature account
|
||||||
|
@ -447,7 +448,7 @@ impl Bridge {
|
||||||
program_id,
|
program_id,
|
||||||
&Self::derive_signature_seeds(bridge, hash, guardian_index),
|
&Self::derive_signature_seeds(bridge, hash, guardian_index),
|
||||||
)?
|
)?
|
||||||
.0)
|
.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn derive_key(
|
pub fn derive_key(
|
||||||
|
@ -478,6 +479,15 @@ impl Bridge {
|
||||||
}
|
}
|
||||||
panic!("Unable to find a viable program address nonce");
|
panic!("Unable to find a viable program address nonce");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tx fee of Signature checks and PostVAA (see docs for calculation)
|
||||||
|
pub const VAA_TX_FEE: u64 = 18 * 10000;
|
||||||
|
|
||||||
|
pub fn transfer_fee() -> u64 {
|
||||||
|
// Pay for 2 signature state and Claimed VAA rents + 2 * guardian tx fees
|
||||||
|
// This will pay for this transfer and ~10 inbound ones
|
||||||
|
Rent::default().minimum_balance((size_of::<SignatureState>() + size_of::<ClaimedVAA>()) * 2) + Self::VAA_TX_FEE * 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check is a token state is initialized
|
/// Check is a token state is initialized
|
||||||
|
|
|
@ -190,6 +190,7 @@ fn command_lock_tokens(
|
||||||
&[],
|
&[],
|
||||||
amount,
|
amount,
|
||||||
)?,
|
)?,
|
||||||
|
system_instruction::transfer(&config.owner.pubkey(), &bridge_key, Bridge::transfer_fee()),
|
||||||
transfer_out(
|
transfer_out(
|
||||||
bridge,
|
bridge,
|
||||||
&config.owner.pubkey(),
|
&config.owner.pubkey(),
|
||||||
|
@ -207,7 +208,7 @@ fn command_lock_tokens(
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"custody: {}, ",
|
"custody: {}, ",
|
||||||
instructions[1].accounts[8].pubkey.to_string()
|
instructions[2].accounts[8].pubkey.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut transaction =
|
let mut transaction =
|
||||||
|
@ -232,7 +233,7 @@ fn check_fee_payer_balance(config: &Config, required_balance: u64) -> Result<(),
|
||||||
lamports_to_sol(required_balance),
|
lamports_to_sol(required_balance),
|
||||||
lamports_to_sol(balance)
|
lamports_to_sol(balance)
|
||||||
)
|
)
|
||||||
.into())
|
.into())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -247,7 +248,7 @@ fn check_owner_balance(config: &Config, required_balance: u64) -> Result<(), Err
|
||||||
lamports_to_sol(required_balance),
|
lamports_to_sol(required_balance),
|
||||||
lamports_to_sol(balance)
|
lamports_to_sol(balance)
|
||||||
)
|
)
|
||||||
.into())
|
.into())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1313,31 +1314,31 @@ fn main() {
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
.and_then(|transaction| {
|
.and_then(|transaction| {
|
||||||
if let Some(transaction) = transaction {
|
if let Some(transaction) = transaction {
|
||||||
// TODO: Upgrade to solana-client 1.3 and
|
// TODO: Upgrade to solana-client 1.3 and
|
||||||
// `send_and_confirm_transaction_with_spinner_and_commitment()` with single
|
// `send_and_confirm_transaction_with_spinner_and_commitment()` with single
|
||||||
// confirmation by default for better UX
|
// confirmation by default for better UX
|
||||||
let signature = config
|
let signature = config
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.send_and_confirm_transaction_with_spinner_and_config(
|
.send_and_confirm_transaction_with_spinner_and_config(
|
||||||
&transaction,
|
&transaction,
|
||||||
config.commitment_config,
|
config.commitment_config,
|
||||||
RpcSendTransactionConfig {
|
RpcSendTransactionConfig {
|
||||||
// TODO: move to https://github.com/solana-labs/solana/pull/11792
|
// TODO: move to https://github.com/solana-labs/solana/pull/11792
|
||||||
skip_preflight: true,
|
skip_preflight: true,
|
||||||
preflight_commitment: None,
|
preflight_commitment: None,
|
||||||
encoding: None,
|
encoding: None,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
println!("Signature: {}", signature);
|
println!("Signature: {}", signature);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
eprintln!("{}", err);
|
eprintln!("{}", err);
|
||||||
exit(1);
|
exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keypair_from_seed_arg(arg_matches: &ArgMatches) -> Keypair {
|
fn keypair_from_seed_arg(arg_matches: &ArgMatches) -> Keypair {
|
||||||
|
@ -1352,8 +1353,8 @@ fn keypair_from_seed_arg(arg_matches: &ArgMatches) -> Keypair {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_u8<T>(amount: T) -> Result<(), String>
|
pub fn is_u8<T>(amount: T) -> Result<(), String>
|
||||||
where
|
where
|
||||||
T: AsRef<str> + Display,
|
T: AsRef<str> + Display,
|
||||||
{
|
{
|
||||||
if amount.as_ref().parse::<u8>().is_ok() {
|
if amount.as_ref().parse::<u8>().is_ok() {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1366,8 +1367,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_u32<T>(amount: T) -> Result<(), String>
|
pub fn is_u32<T>(amount: T) -> Result<(), String>
|
||||||
where
|
where
|
||||||
T: AsRef<str> + Display,
|
T: AsRef<str> + Display,
|
||||||
{
|
{
|
||||||
if amount.as_ref().parse::<u32>().is_ok() {
|
if amount.as_ref().parse::<u32>().is_ok() {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1380,8 +1381,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_hex<T>(value: T) -> Result<(), String>
|
pub fn is_hex<T>(value: T) -> Result<(), String>
|
||||||
where
|
where
|
||||||
T: AsRef<str> + Display,
|
T: AsRef<str> + Display,
|
||||||
{
|
{
|
||||||
hex::decode(value.to_string())
|
hex::decode(value.to_string())
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@project-serum/sol-wallet-adapter": "^0.1.1",
|
"@project-serum/sol-wallet-adapter": "^0.1.1",
|
||||||
"@solana/spl-token": "^0.0.11",
|
"@solana/spl-token": "^0.0.11",
|
||||||
"@solana/web3.js": "^0.80.2",
|
"@solana/web3.js": "^0.87.1",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"@testing-library/user-event": "^7.1.2",
|
||||||
|
|
|
@ -192,11 +192,17 @@ function Assistant() {
|
||||||
decimals: transferData.fromCoinInfo.decimals,
|
decimals: transferData.fromCoinInfo.decimals,
|
||||||
}, Math.random() * 100000);
|
}, Math.random() * 100000);
|
||||||
let ix = spl.Token.createApproveInstruction(TOKEN_PROGRAM, new PublicKey(transferData.fromCoinInfo.address), await bridge.getConfigKey(), k.publicKey, [], transferData.amount.toNumber())
|
let ix = spl.Token.createApproveInstruction(TOKEN_PROGRAM, new PublicKey(transferData.fromCoinInfo.address), await bridge.getConfigKey(), k.publicKey, [], transferData.amount.toNumber())
|
||||||
|
let bridge_account = await bridge.getConfigKey();
|
||||||
|
let fee_ix = solanaWeb3.SystemProgram.transfer({
|
||||||
|
fromPubkey: k.publicKey,
|
||||||
|
toPubkey: bridge_account,
|
||||||
|
lamports: await bridge.getTransferFee()
|
||||||
|
});
|
||||||
let recentHash = await c.getRecentBlockhash();
|
let recentHash = await c.getRecentBlockhash();
|
||||||
let tx = new Transaction();
|
let tx = new Transaction();
|
||||||
tx.recentBlockhash = recentHash.blockhash
|
tx.recentBlockhash = recentHash.blockhash
|
||||||
tx.add(ix)
|
tx.add(ix)
|
||||||
|
tx.add(fee_ix)
|
||||||
tx.add(lock_ix)
|
tx.add(lock_ix)
|
||||||
tx.sign(k)
|
tx.sign(k)
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -78,11 +78,18 @@ function TransferSolana() {
|
||||||
decimals: Math.min(coinInfo.decimals, 9)
|
decimals: Math.min(coinInfo.decimals, 9)
|
||||||
}, Math.random() * 100000);
|
}, Math.random() * 100000);
|
||||||
let ix = spl.Token.createApproveInstruction(TOKEN_PROGRAM, fromAccount, await bridge.getConfigKey(), k.publicKey, [], transferAmount.toNumber())
|
let ix = spl.Token.createApproveInstruction(TOKEN_PROGRAM, fromAccount, await bridge.getConfigKey(), k.publicKey, [], transferAmount.toNumber())
|
||||||
|
let bridge_account = await bridge.getConfigKey();
|
||||||
|
let fee_ix = solanaWeb3.SystemProgram.transfer({
|
||||||
|
fromPubkey: k.publicKey,
|
||||||
|
toPubkey: bridge_account,
|
||||||
|
lamports: await bridge.getTransferFee()
|
||||||
|
});
|
||||||
|
|
||||||
let recentHash = await c.getRecentBlockhash();
|
let recentHash = await c.getRecentBlockhash();
|
||||||
let tx = new Transaction();
|
let tx = new Transaction();
|
||||||
tx.recentBlockhash = recentHash.blockhash
|
tx.recentBlockhash = recentHash.blockhash
|
||||||
tx.add(ix)
|
tx.add(ix)
|
||||||
|
tx.add(fee_ix)
|
||||||
tx.add(lock_ix)
|
tx.add(lock_ix)
|
||||||
tx.sign(k)
|
tx.sign(k)
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -105,6 +105,7 @@ class SolanaBridge {
|
||||||
{pubkey: this.tokenProgram, isSigner: false, isWritable: false},
|
{pubkey: this.tokenProgram, isSigner: false, isWritable: false},
|
||||||
{pubkey: solanaWeb3.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
|
{pubkey: solanaWeb3.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
|
||||||
{pubkey: solanaWeb3.SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
|
{pubkey: solanaWeb3.SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
|
||||||
|
{pubkey: solanaWeb3.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false},
|
||||||
{pubkey: tokenAccount, isSigner: false, isWritable: true},
|
{pubkey: tokenAccount, isSigner: false, isWritable: true},
|
||||||
{pubkey: configKey, isSigner: false, isWritable: false},
|
{pubkey: configKey, isSigner: false, isWritable: false},
|
||||||
|
|
||||||
|
@ -418,6 +419,11 @@ class SolanaBridge {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return (await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID))[0];
|
return (await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTransferFee(): Promise<number> {
|
||||||
|
// Reference processor.rs::Bridge::transfer_fee
|
||||||
|
return (await this.connection.getMinimumBalanceForRentExemption((37 + 1337) * 2)) + 18 * 10000 * 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Taken from https://github.com/solana-labs/solana-program-library
|
// Taken from https://github.com/solana-labs/solana-program-library
|
||||||
|
|
Loading…
Reference in New Issue