From Zero to Audited: My First Contract Security Audit

ETHBoston 2025 · 12 Sept 2025 · 16:50

Shaun Scovil shares lessons from auditing EVMAuth, a token-based authorization system. He explains a gas-exhaustion vulnerability involving expiring tokens, mitigations using expiration time buckets and pruning, and the importance of fuzz testing and independent audits.

Speakers

Topics

From ETHBoston 2025, hosted by ETHBoston.

Transcript

All right. Uh, so quick show of hands. How many of you here have written a smart contract? Right on. And have you ever gone through a security audit? Awesome. Very stressful. Um, so I just I I've been in crypto since 2015, but I just started writing smart contracts and went through my first security audit. And so I just wanted to share my learnings and hopefully uh help everybody out. So, first of all, who am I? My name is Sean Scoville, uh, principal engineer at Radius. Uh, you know, like I said, I started in 2015 in crypto at Circle, spent four years there, another three years at Paloniex.

Then I ran like hell from the crypto industry for a couple of years and worked in a completely different space and then I came back to it and now I work with a bunch of great people that I used to work with at Circle now at Radius. Um, okay. So one of the things that I've done in my time at Radius is create uh EBM O right so this is a smart contract that is a it's built on existing token standards right so it's a token contract that you can deploy yourself um and you can either use ERC1155 or 6909 uh whichever flavor you like um but it has some enhancements over that regular token standard with the intent of using it for authorization state management right so these These tokens can be purchased directly from

your customers. So if you have like subscription ser services, memberships, things like that. Um you can accept native currency or any stable coin or ERC20 token. You can make these tokens expire automatically just by setting a TTL. Um and each contract you can have any number of tokens. So you can have some that expire, some that don't. Uh you can make any of these tokens non-transferable. So you can use them for like identity tokens or whatever. You can just issue them directly to people. Um, and you can do things like freeze people's accounts, pause the contract, bunch of other stuff, right?

Whole bunch of great functionality baked into it. I think it's super useful. And as somebody who has spent a lot of time building REST APIs and authorization servers and account management systems, um, I just know from experience that there's a lot of work involved in that. And what EVM O does for you is it actually lets you offload all of that into a smart contract. Now you don't have to have, you know, databases and O services up and running and things like that. You just verify that somebody owns a crypto wallet address. And then if they have the a balance of the token that you require, your API just lets them pass. If not, you can respond with a 402. Tell them, you know, exactly how to go about acquiring one of these tokens. They can purchase it directly, give them the price, tell them how long

it lasts, all that. Great. So, one of the uh one of the features, there's a lot of features here to unpack and I'm not going to try and go through them all today, but uh one that I want to zoom in on is the expiring tokens because I think this is the most interesting and challenging thing that I had to build into this contract. It's like how do you get these tokens to expire automatically over time? Um, and so basically what it comes down to is you cannot just you can't just store a balance and an expiration timestamp because if I purchase tokens today and they last for 30 days and then I purchase more in 29 days, you can't just update that expiration timestamp. Like

you need two separate expiration timestamps. So you have to store this information in a way that is like you know coupled between the amount of tokens that you purchased and the expiration for those particular tokens. Um and that creates all kinds of other challenges because now you have to store a mapping of you know a wallet address to a token ID to an array of all of these different groups of token balances that have different expiration timestamps. Um I wrote a whole blog post about it. I'll share the slides around too. Um there's a link to it there. But long story short, that was a really tough nut to crack. And I thought that I thought of everything when I went through it. You know, thinking about like making sure that uh you know, the oldest tokens are used first if you're burning them or if they're getting

transferred. Um you can only you can't make state changing um like when somebody just checks a balance, right? You can't update or like clean up those arrays. So you can only do it when they're, you know, when a minting or burning or transfer action is happening because you don't want unbounded array growth. Took care of all that with the uh the architecture that's there. Felt really good about it. But because of the nature of this contract, seemed like a good idea to have a security audit. And so we did.

And sure enough, they found a vulnerability. And it was something that I just never even occurred to me to think about, right? This is a griefing attack. Basically, the idea is that an attacker could purposefully stuff your account with a bunch of tokens that expire like one second apart. And now that array just gets really big because you've got all these tokens that are, you know, going to expire at different times. And then when that cleanup mechanism that that I've set up to automatically expire these tokens and and clean up the array happens, your transactions will run out of gas and you basically won't be able to interact with that token anymore. Now that's just that was just the furthest thing from my mind but it was brilliant, right? It's like that is valid. Somebody can affect somebody else's experience on

it. You have to address that. So it's important to note that there were some limitations to this attack vector and so that made me feel a little bit better about it, right? Um so tokens that didn't expire, you set the TTL to zero, not affected. um tokens that have a very short TTL. If each expiration time stamp is a second of time, right, because it's measured in seconds, um and you only allow up to 60 seconds for the token, then fine, you'll be all right. But the more tokens you allow, the longer the TTL is, the more susceptible you are. Um and then the other thing is tokens that couldn't be purchased directly, right?

You set the the price to zero, nobody can purchase them. Fine. Right now it's unless your application just lets the attacker mint these tokens at will, you're probably not affected. So that was a relief, but still a problem. Thought about a lot of different solutions, right? So one of them just make any ephemeral token not transferable, right? Take away the problem. But that kind of sucks, right? You want to be able to transfer the this this contract should be as flexible as possible, right? um looked at different ways that you could limit the gas consumption of the pruning function that goes in and cleans up the array. So maybe after a certain amount of gas it just stops. But again like there's there's problems with that as well. Uh we could make the expired token pruning

method public so that at least there's a way that anybody can call that method without you know outside of the context of uh te token transferring or minting or burning uh and clean things up. But that's a manual process. So it's not ideal. It's helpful but not a great fix. Um we could implement a maximum number of balance records per address per token, right? So like maybe you just have a bounded array. The most balance records you could have is maybe 30 or something like that. You can do that, but then what happens at 31, right? Like somebody goes to purchase tokens, they just can't purchase anymore. What if they need them, right? So that wasn't great. Um so what we ended up doing was um using

expiration time buckets right so the idea is that we're going to sacrifice a little bit of precision in order to greatly reduce the amount or the size of that array right and so the number I ended up landing on at first I was doing like 30 did some testing with the new version of the contract you can go up to 100 fine um very safely so so now basically you take a TTL the the time that a token has to expire and you can guarantee that the minimum amount of time that that token will be valid is that TTL or the maximum would be that t that TTL plus 1% minus 1 second right so there's a little bit of a range but it's a sacrifice we make to make sure that nobody messes with you right this is why

we can't have nice things but um it was just it was a really interesting problem to solve because there were a lot of different trade-offs a lot of different ways that you could go about something like this um There were also a few other uh risk mitigations. So coming back to making that pruning function public. So now anybody can call that that's helpful. Um one of the other things that really helped was making the contract upgradeable for a couple of reasons. First of all, it should have been upgradeable from the beginning. I just didn't know how to do that. Um but making it upgradeable does two things, right? One, when more vulnerabilities or issues come up at some point, we can fix that. Um but then the other thing is by having it be upgradable the logic lives

in a separate contract if if you deploy it as a proxy um the logic lives in one contract the data storage lives in another one that you can control and the amount of gas that all these operations take is significantly reduced. So, that was pretty cool. But key takeaways here. Um, mainly test your contracts. Um, you know, definitely be creative. Consider the many ways that an attacker could really just negatively impact the experience for other users of your contract. Um, one thing is a lot of this could have been caught earlier just by having better test coverage. So, the the latest version of EVM is a lot more tests. Um, and one thing that I did was

use fuzz tests so that you're not just like coming up with a random like a specific set of variables, right? It'll just pass in different numbers each time. Uh, really exercise uh the function much better. Um, and then an independent security audit I can verify is, you know, it's worth every token that you spend on it. Um, and so would highly recommend it. Um, all right. And I think I got through that way quicker than I expected. So, anybody have any questions? Oh, I think they're gonna bring a mic.

>> Um, I was wondering if you know anything about the status of these AI powered audits. Is this anywhere close to being as good as human audits? And if not, will we get there? >> Oh, I don't know. I mean, I you know, on a long enough timeline, I think yes, everything is going to be way better in the future than it is now. I can't really speak to that. They I worked with some great humans on the security audit. They caught things that I never would have spotted. I think it's going to be some sort of a hybrid, though. It's going to be humans working with AI to make them better at the job of auditing just like it makes us better at the job of developing.

so far. >> Yeah. Yeah. I mean, I think at some stage, you know, maybe humans aren't even the ones writing the code and then that's a whole other topic. But, uh, anybody else >> over here? Hi. How did you pick your auditor? >> Great question. And just so I don't slip, um the audit is still going on and apparently part of our terms of service, I can't mention them by name. Um but they came very highly regarded um our head of security at Radius. I worked with him at Circle and at Paloniex as well. And so he had somebody in mind and uh very reputable company in the

industry. Hey Sean, thanks. That was pretty sick talk. Uh my name is Dan, but I was just curious what is that uh security audit kind of entail? Can you kind of guide us through that process? >> Yeah. Yeah. Yeah. So I mean this company was really great to work with. We had, you know, a shared Slack channel. Um every time the uh engineers assigned to it would come up with something, they would raise the question in the Slack channel. It was very few of us. It was like limited to only a handful of us in that channel. So, not everybody saw it in case it was like a critical vulnerability and we wanted to keep it under wraps until we fixed it. Um, but yeah, it was it was really just upfront you had to sort of identify some of the

issues that you thought might be there, right? So, I knew right away like of all the complexity that expiration logic was the bear. I knew that was going to be the thing that was going to cause a problem if anything um that and the purchase direct purchasing which thankfully worked great. So, um, but yeah, it was it was really just a lot of upfront communication followed by somebody just aggressively going through and testing. And I don't I'm not really familiar with the tools that they used on their end, but this person did a great job. >> Absolutely. >> Any other questions?

>> There you go. Thanks. Um did you discuss with the auditor uh what is the best way to you know create new features or deploy new contracts and code to minimize the amount of auditing that has to be done again over and over? >> Yeah, it's a great question. Um they provided a lot of advice and they were really thought partners in just figuring out how best to mitigate this problem because right like there was a lot of ways that we could have done it. Um so we collaborated on that. We talked through a bunch of different scenarios. um the you know the report that they gave us the initial report was very detailed and pointed out certain things that we needed we needed better documentation we needed way more test coverage than I had um so yeah it was it was very collaborative it wasn't just

sort of like hey we found something wrong go fix it you know >> thank you so much great information I was uh wondering what's your take on recent happenings I know it's kind of like a backdoor and it's not directly related to the smart contracts like that but with within these libraries like the and npm and like what do you think of these you know kind of like back doors that we have because it's not even related to our industry but it's it's related to the whole like what do you think of these libraries open like that and and how can we actually maybe through our industry make it better enforce those securities thank you >> yeah it's a great question if I'm understanding correctly I think you're

talking about supply chain attacks right like maybe I use open zeppelin contracts under the hood what somebody attacks one of those things. And is that is that kind of where you're going with it? that just happened with the >> uh like what happened just recently this this past week um that was there was some some files you know into the the code of the npm libraries right that everybody uses not only just web 3 but in general right and then so compromises like >> heavy security and vulnerabilities overall not just web 3 but like in general what is your take on that and how we maybe we can through our protocols in web3 like help organize all

those libraries or have some sort of more consistent security to those type of open libraries like that. just wondering >> I to that I would just say there's always going to be vulnerability as long as there's a financial incentive to hack your code there is going to be somebody trying to hack your code right like and the more popular it is the more widely used it is the the more at risk it is um I will go back to my circle days uh Scott Matsumoto who was our head of security at the time he said something great to me he was just notoriously known for being the person who said no to every engineer every time they wanted to do anything like he would just nope, we're not going to do that. And so I had this feature that I was going to add to

the product, but I was like really nervous that he was gonna say no. And so I came to him and I'm like, "Hey, you know, I want to do this. I I don't think you're going to say yes because this this this and I gave him all the reasons why." And he said, "Absolutely do it." And I was like, "What? Why? Why did you do that?" He's like, "I'm not worried because you are." And I think that is the best advice I've ever gotten. Right? It's like if you're not worried about it as the developer, then somebody else has to be worried about it. So we all security is a shared responsibility, right? We all have to be worried about it and think about it and Tesla have our code. That's that's about the best answer I can give you. I don't have a solution.

All right, we have time for one last really quick question. if anyone has one more question. If not, all right, everyone, let's give Shawn a big round of applause.